전체 목록
브라우저Medium#097

브라우저의 Critical Rendering Path(CRP)를 설명해주세요.

#브라우저#CRP#렌더링#성능최적화

답변 포인트

HTML 파싱부터 화면에 픽셀이 그려지기까지의 단계를 생각해보세요.

정답 및 해설

빠른 요약

CRP는 브라우저가 HTML/CSS/JS를 받아 화면에 픽셀을 그리는 과정입니다. 단계: 1.

Critical Rendering Path(CRP, 핵심 렌더링 경로)는 브라우저가 HTML, CSS, JavaScript를 받아서 화면에 픽셀을 그리기까지의 과정입니다. 이 과정을 이해하면 웹 페이지의 초기 로딩 성능을 최적화하는 데 핵심적인 인사이트를 얻을 수 있습니다. CRP 최적화는 사용자가 페이지를 처음 볼 때까지 걸리는 시간(FCP, LCP)을 단축하는 데 직결됩니다.

CRP 전체 흐름

브라우저
 HTML 
        
1. HTML   DOM 
         ()
2. CSS   CSSOM 
        
3. DOM + CSSOM  Render Tree 
        
4. Layout (Reflow) - / 
        
5. Paint -  
        
6. Composite -  
        
 

1단계: HTML 파싱 → DOM 생성

HTML
<!--  HTML  DOM(Document Object Model)   -->
<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="style.css">  <!-- CSS    -->
    <script src="app.js"></script>            <!-- JS: DOM  ! -->
  </head>
  <body>
    <h1></h1>
    <p></p>
  </body>
</html>
브라우저
DOM :
Document
 html
     head
        link (rel="stylesheet")
        script (src="app.js")
     body
         h1 ("안녕하세요")
         p ("본문입니다")

JavaScript는 DOM 파싱을 차단한다

HTML
<!--   script : DOM   JS    -->
<head>
  <script src="heavy-lib.js"></script>  <!--   +    -->
</head>
<body>
  <!-- heavy-lib.js      -->
  <h1>  </h1>
</body>

<!--  defer: DOM    , DOMContentLoaded   -->
<script src="app.js" defer></script>

<!--  async:  ,     (  ) -->
<!--      -    -->
<script src="analytics.js" async></script>

<!--   :  defer  -->
<script type="module" src="app.js"></script>

2단계: CSS 파싱 → CSSOM 생성

CSS
/* CSS 파싱 → CSSOM(CSS Object Model) 생성 */
body {
  font-size: 16px;
  color: #333;
}

h1 {
  font-size: 2em;  /* 상속: body의 font-size 기반 → 32px */
}

p.highlight {
  color: blue;
}
브라우저
CSSOM :
body (font-size: 16px, color: #333)
 h1 (font-size: 32px, color: #333 [])
 p.highlight (color: blue)

CSS는 렌더링을 차단한다

HTML
<!-- CSS    (Render-blocking resource) -->
<!-- CSSOM   Render Tree       -->

<!--   CSS  -->
<link rel="stylesheet" href="large-styles.css">

<!--       -->
<!--  CSS     -->
<link rel="stylesheet" href="print.css" media="print">
<link rel="stylesheet" href="mobile.css" media="(max-width: 768px)">

<!--  Critical CSS  -->
<head>
  <style>
    /* 위에 보이는 영역(above the fold)의 핵심 CSS만 인라인으로 포함 */
    body { margin: 0; font-family: sans-serif; }
    header { background: #fff; }
    .hero { display: flex; ... }
  </style>
  <!--  CSS   -->
  <link rel="preload" href="styles.css" as="style" onload="this.rel='stylesheet'">
</head>

3단계: Render Tree 생성

DOM과 CSSOM을 결합하여 실제로 화면에 그려질 요소만 포함하는 Render Tree를 생성합니다.

브라우저
DOM   Render Tree  :
- display: none   Render Tree  (  )
- visibility: hidden  Render Tree  ( ,  )
- <head>, <script>, <style>      
- ::before, ::after    

DOM                    Render Tree
document               body
 head ()          h1 "안녕하세요" [font-size:32px, color:#333]
 body                 p "본문" [color:#333]
    h1
    p
    div [display:none] (!)

4단계: Layout (Reflow) - 위치와 크기 계산

Render Tree의 각 노드가 화면에서 차지할 정확한 위치와 크기를 계산합니다.

JavaScript
// Reflow를 유발하는 CSS 속성들 (비용이 큼)
// 변경 시 전체 또는 일부 레이아웃 재계산 필요
const reflowProperties = [
  'width', 'height', 'margin', 'padding',
  'border', 'position', 'top', 'left', 'right', 'bottom',
  'display', 'float', 'overflow',
  'font-size', 'font-weight', 'line-height'
];

// ❌ Reflow를 강제로 발생시키는 코드 (레이아웃 스래싱)
function badAnimation() {
  const el = document.getElementById('box');
  for (let i = 0; i < 100; i++) {
    el.style.left = el.offsetLeft + 1 + 'px';  // 읽기(offsetLeft) + 쓰기(style.left) 반복
    // → 매번 Reflow 강제 발생!
  }
}

// ✅ Reflow 최소화
function goodAnimation() {
  const el = document.getElementById('box');
  let left = el.offsetLeft;  // 한 번만 읽기
  for (let i = 0; i < 100; i++) {
    left += 1;
  }
  el.style.left = left + 'px';  // 한 번만 쓰기
}

// ✅ CSS transform 사용 (Layout 단계 건너뜀)
el.style.transform = 'translateX(100px)';  // Composite 단계만 실행

5단계: Paint - 픽셀 채우기

Layout 단계에서 계산된 위치에 실제 픽셀을 그립니다. 색상, 배경, 그림자, 텍스트 등을 그립니다.

JavaScript
// Repaint를 유발하는 CSS 속성 (Layout은 변경 안 하고 그리기만 다시)
const repaintProperties = [
  'color', 'background-color', 'visibility',
  'outline', 'border-radius', 'box-shadow',
  'text-decoration'
];

// Repaint는 Reflow보다 비용이 적지만, 여전히 비쌈
// 가능하면 Composite 단계만 실행되도록 최적화

6단계: Composite - 레이어 합성

여러 레이어를 최종적으로 합성하여 화면에 출력합니다.

JavaScript
// GPU에서 처리 가능한 CSS 속성 (Composite 단계만 실행 → 가장 효율적)
const compositeOnlyProperties = [
  'transform',    // translateX, translateY, scale, rotate 등
  'opacity',      // 투명도
  'filter',       // blur 등 (일부)
];

// ✅ 애니메이션 최적화: transform과 opacity만 사용
.animated-element {
  /* ❌ 느림: width 변경은 Layout → Paint → Composite 순서로 실행 */
  transition: width 0.3s ease;

  /* ✅ 빠름: transform은 Composite 단계만 실행 (GPU 가속) */
  transition: transform 0.3s ease;
}

// will-change로 레이어 사전 생성 (과도한 사용 주의)
.panel {
  will-change: transform;  /* 이 요소가 변환될 것임을 브라우저에 힌트 */
}

렌더링 단계 트리거 비교

브라우저
          

width, height   Layout  Paint  Composite  ( )
color           Paint  Composite
transform       Composite                 ( )
opacity         Composite                 (GPU )

CRP 최적화 전략

1. 렌더 차단 리소스 최소화

HTML
<!-- Critical CSS  -->
<head>
  <style>
    /* Above the fold 영역의 핵심 스타일만 */
    body { margin: 0; }
    .header { height: 60px; background: white; }
    .hero { min-height: 400px; }
  </style>

  <!--  CSS   -->
  <link rel="preload" href="full-styles.css" as="style"
        onload="this.onload=null;this.rel='stylesheet'">
  <noscript><link rel="stylesheet" href="full-styles.css"></noscript>
</head>

<!-- JS defer/async  -->
<script src="analytics.js" async></script>
<script src="app.js" defer></script>

2. 리소스 우선순위 힌트

HTML
<!--     -->
<link rel="preload" href="hero-image.jpg" as="image">
<link rel="preload" href="main-font.woff2" as="font" crossorigin>

<!--      ( ) -->
<link rel="prefetch" href="/next-page.html">

<!-- DNS   -->
<link rel="dns-prefetch" href="//api.example.com">

<!--    -->
<link rel="preconnect" href="https://fonts.googleapis.com">

3. Reflow 최소화

JavaScript
// ✅ 여러 스타일 변경은 한 번에
const el = document.getElementById('box');

// ❌ 각각 Reflow 발생
el.style.width = '100px';
el.style.height = '100px';
el.style.margin = '10px';

// ✅ 클래스로 한 번에 변경
el.classList.add('resized');

// ✅ DocumentFragment로 DOM 조작 최소화
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
  const li = document.createElement('li');
  li.textContent = `항목 ${i}`;
  fragment.appendChild(li);
}
document.getElementById('list').appendChild(fragment);  // DOM에 한 번만 추가

4. requestAnimationFrame 활용

JavaScript
// ✅ 애니메이션은 rAF로 처리 (브라우저 렌더링 주기에 맞춤)
function animate() {
  element.style.transform = `translateX(${position}px)`;
  position += 1;
  if (position < 300) {
    requestAnimationFrame(animate);  // 다음 프레임에 실행
  }
}
requestAnimationFrame(animate);

성능 측정 지표

지표의미목표
FCP (First Contentful Paint)첫 번째 콘텐츠 표시 시간1.8초 이하
LCP (Largest Contentful Paint)가장 큰 콘텐츠 표시 시간2.5초 이하
CLS (Cumulative Layout Shift)레이아웃 이동 누적 점수0.1 이하
TTI (Time to Interactive)완전히 인터랙션 가능한 시간3.8초 이하

정리

단계역할차단 요소최적화 방법
HTML 파싱DOM 트리 생성<script> 태그defer / async 속성
CSS 파싱CSSOM 트리 생성CSS 파일Critical CSS 인라인화
Render Tree표시할 요소 결정CSSOM 완성 전까지 대기불필요한 노드 최소화
Layout위치/크기 계산width, height 변경transform 사용, 배치 계산 최소화
Paint픽셀 채우기color, background 변경레이어 분리
Composite레이어 합성-GPU 가속 속성 (transform, opacity) 활용

핵심: CRP 최적화의 핵심은 "렌더 차단 리소스를 줄이고, Layout과 Paint 단계를 최소화하며, Composite 단계만 실행되는 CSS 속성(transform, opacity)으로 애니메이션을 구현하는 것"입니다.

관련 질문

같은 카테고리/태그 기준