크리티컬 렌더링 패스(Critical Rendering Path, CRP)는 브라우저가 HTML을 받아 화면에 픽셀을 그리기까지의 일련의 단계다. HTML → DOM, CSS → CSSOM, JavaScript 실행 → 렌더 트리 → 레이아웃 → 페인트 → 컴포지트 순서로 진행된다.
렌더링 파이프라인
1. HTML 파싱 → DOM 트리 생성
2. CSS 파싱 → CSSOM 트리 생성
3. JavaScript 실행 (파서 블로킹 주의)
4. DOM + CSSOM → 렌더 트리 생성
5. Layout (Reflow): 요소 위치/크기 계산
6. Paint: 픽셀 색상 채우기
7. Composite: 레이어 합성
렌더 블로킹 리소스 최적화
html
<!DOCTYPE html>
<html>
<head>
<!-- CSS: 렌더 블로킹 → 인라인 크리티컬 CSS 또는 preload -->
<link rel="preload" href="styles.css" as="style" onload="this.rel='stylesheet'">
<style>
/* 크리티컬 CSS 인라인 */
body { margin: 0; font-family: sans-serif; }
.hero { background: #3b82f6; }
</style>
<!-- JS: 파서 블로킹 → async/defer 사용 -->
<script src="analytics.js" async></script> <!-- 비동기, 순서 무관 -->
<script src="app.js" defer></script> <!-- DOMContentLoaded 후 실행 -->
</head>
</html>
Reflow vs Repaint
javascript
// Reflow 유발 (비용 큼): 기하 변경
element.style.width = '200px';
element.style.marginTop = '10px';
const height = element.offsetHeight; // 강제 동기 레이아웃!
// Repaint만 유발 (중간 비용): 색상, 배경
element.style.color = 'red';
element.style.backgroundColor = 'blue';
// 컴포지트만 (비용 작음): transform, opacity
element.style.transform = 'translateX(100px)'; // GPU 가속
element.style.opacity = '0.5';
// 일괄 DOM 변경으로 최적화
requestAnimationFrame(() => {
// 읽기 먼저
const width = element.offsetWidth;
const height = element.offsetHeight;
// 쓰기 나중에 (배치)
element.style.transform = `translate(${width}px, ${height}px)`;
});
성능 측정
javascript
// PerformanceObserver로 렌더링 타이밍 측정
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(entry.name, entry.startTime, entry.duration);
}
});
observer.observe({ type: 'paint', buffered: true });
observer.observe({ type: 'layout-shift', buffered: true });
관련 개념