[Daily morning study] 브라우저 렌더링 과정 (Critical Rendering Path)

#daily morning study

Image


브라우저 렌더링 과정 (Critical Rendering Path)

브라우저가 HTML 파일을 받아서 화면에 픽셀을 그리기까지의 과정을 Critical Rendering Path(CRP)라고 한다. 이 과정을 이해하면 왜 특정 리소스가 렌더링을 막는지, 어떻게 최적화해야 하는지 파악할 수 있다.


전체 흐름 요약

HTML 파싱 → DOM 생성
CSS 파싱  → CSSOM 생성
              ↓
         Render Tree 생성
              ↓
           Layout (Reflow)
              ↓
            Paint
              ↓
          Compositing

1. HTML 파싱 → DOM 생성

브라우저는 서버에서 받은 HTML 바이트를 문자열로 변환하고, 토크나이저가 토큰으로 분리한 뒤 트리 구조의 DOM(Document Object Model)을 만든다.

  • 태그를 만나면 노드를 생성하고 부모-자식 관계를 설정한다.
  • <script> 태그를 만나면 파싱을 즉시 중단하고 스크립트를 실행한다. (렌더 블로킹)
  • <img> 같은 외부 리소스는 다운로드 요청만 보내고 파싱은 계속한다.
<!-- 파서는 이 구조를 DOM 트리로 변환한다 -->
<html>
  <head><title>Hello</title></head>
  <body>
    <h1>제목</h1>
    <p>본문</p>
  </body>
</html>

2. CSS 파싱 → CSSOM 생성

CSS는 DOM과 별개로 CSSOM(CSS Object Model)을 만든다. DOM처럼 트리 구조이며, 선택자 특이성(Specificity)과 상속 관계를 적용한 최종 스타일 값을 담는다.

  • CSS는 렌더 블로킹 리소스다. CSSOM이 완성될 때까지 Render Tree 생성을 기다린다.
  • CSS 규칙은 오른쪽에서 왼쪽으로 파싱된다. div .title p 같은 선택자는 먼저 p를 찾고, .title의 자식인지 확인하는 방식이다.
body { font-size: 16px; }
h1   { font-size: 2em; color: #333; }  /* 32px 계산됨 */

3. Render Tree 생성

DOM과 CSSOM을 결합해서 화면에 실제로 보이는 요소만 남긴 Render Tree를 만든다.

  • display: none 요소는 Render Tree에 포함되지 않는다.
  • visibility: hidden은 공간을 차지하므로 포함된다.
  • <head>, <script>, <meta> 등 화면에 안 보이는 태그는 제외된다.

각 노드에는 적용된 CSS 스타일이 포함된다(Computed Style).


4. Layout (Reflow)

Render Tree의 각 노드가 화면 어디에, 얼마나 큰 크기로 배치될지 계산한다.

  • 뷰포트 크기를 기준으로 %, em, vh 등 상대적 단위를 픽셀로 환산한다.
  • 박스 모델(margin, padding, border)을 반영한다.
  • 이 단계를 Reflow라고도 부른다.

Reflow는 비용이 크다. 한 요소의 크기가 바뀌면 부모 및 자식 요소까지 재계산해야 하기 때문이다.

// 이렇게 하면 Reflow가 여러 번 발생할 수 있다
el.style.width = '100px';
el.style.height = '200px';
el.style.top = '50px';

// 한 번에 변경하면 Reflow 횟수를 줄일 수 있다
el.style.cssText = 'width: 100px; height: 200px; top: 50px;';

5. Paint (Rasterization)

Layout 결과를 바탕으로 각 요소를 픽셀로 변환한다. 색상, 배경, 테두리, 텍스트, 그림자 등 시각적 속성을 레이어(Layer)에 그린다.

  • opacity, transform 등 일부 속성은 별도 레이어를 만든다.
  • 레이어가 많아지면 메모리 사용량이 증가하므로 무분별하게 will-change를 남발하면 안 된다.

6. Compositing

여러 레이어를 GPU가 최종적으로 합성해서 화면에 출력한다.

  • transform, opacity 변화는 Layout과 Paint를 건너뛰고 이 단계만 실행된다. → 애니메이션에 유리하다.
  • top, left 변화는 Layout부터 다시 실행된다. → 애니메이션에 비효율적이다.
/* Layout + Paint + Composite 전부 트리거 */
.bad  { position: relative; top: 10px; }

/* Composite 단계만 트리거 */
.good { transform: translateY(10px); }

렌더 블로킹 리소스

리소스블로킹 여부이유
CSS (<link>)렌더 블로킹CSSOM 없이 Render Tree 못 만듦
JS (<script>)파서 블로킹스크립트가 DOM을 수정할 수 있음
JS (async)비블로킹다운로드는 병렬, 실행만 파싱 중단
JS (defer)비블로킹DOM 완성 후 실행, DOMContentLoaded 전
<!-- 파서 블로킹 -->
<script src="app.js"></script>

<!-- 다운로드만 병렬, 실행 시점에 파싱 중단 -->
<script src="app.js" async></script>

<!-- DOM 완성 후 실행 (순서 보장) -->
<script src="app.js" defer></script>

Reflow vs Repaint 차이

구분트리거 속성 예시재실행 단계
Reflowwidth, height, font-size, margin, topLayout → Paint → Composite
Repaintcolor, background-color, visibilityPaint → Composite
Composite onlytransform, opacityComposite

Reflow가 가장 비용이 크고 Composite만 발생하는 게 가장 빠르다.


주요 최적화 기법

Critical CSS 인라인 삽입

첫 화면(above-the-fold)에 필요한 CSS만 <style> 태그로 인라인으로 넣으면 외부 CSS 다운로드를 기다리지 않아도 된다.

<head>
  <style>/* 핵심 스타일만 인라인 */</style>
  <link rel="stylesheet" href="full.css" media="print" onload="this.media='all'">
</head>

preload, prefetch

<!-- 현재 페이지에 곧 필요한 리소스 미리 로드 -->
<link rel="preload" href="font.woff2" as="font" crossorigin>

<!-- 다음 페이지에 필요할 것 같은 리소스 선제 다운로드 -->
<link rel="prefetch" href="next-page.js">

배치 DOM 업데이트 (DocumentFragment 활용)

const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
  const li = document.createElement('li');
  li.textContent = `item ${i}`;
  fragment.appendChild(li);
}
// DOM에는 한 번만 붙인다 → Reflow 1회
document.getElementById('list').appendChild(fragment);

정리

  • CRP는 DOM → CSSOM → Render Tree → Layout → Paint → Composite 순서로 진행된다.
  • CSS는 렌더 블로킹, 일반 <script>는 파서 블로킹 리소스다.
  • transform, opacity 기반 애니메이션은 GPU Composite 단계만 타서 성능이 좋다.
  • top/left 대신 transform, color 대신 opacity를 활용하면 Reflow를 피할 수 있다.
  • DOM 조작은 배치(batch)로 처리해서 Reflow 횟수를 최소화한다.