브라우저 렌더링 과정(Critical Rendering Path) 완벽 이해하기
웹 페이지가 사용자의 눈에 보이기까지 브라우저 내부에서 수많은 과정이 일어납니다. 이 과정을 이해하면 로딩 속도를 개선하고, 버그를 줄이며, 사용자 경험을 최적화할 수 있습니다. 이번 글에서는 브라우저 렌더링의 전체 흐름인 Critical Rendering Path를 단계별로 상세히 정리해 드리겠습니다.
Critical Rendering Path란?
Critical Rendering Path(CRP)는 브라우저가 HTML, CSS, JavaScript를 화면의 픽셀로 변환하는 일련의 과정입니다. 이 과정의 각 단계를 최적화하면 페이지 로딩 속도를 크게 개선할 수 있습니다.
[HTML 다운로드] → [DOM 트리 생성]
↓
[CSS 다운로드] → [CSSOM 트리 생성]
↓
[Render Tree 생성]
↓
[Layout (Reflow)]
↓
[Paint]
↓
[Composite]
1단계: DOM 트리 생성
브라우저가 HTML을 파싱하여 DOM(Document Object Model) 트리를 생성합니다. 각 HTML 태그는 노드가 되며, 중첩 관계가 부모-자식 구조로 표현됩니다.
<!DOCTYPE html>
<html>
<head>
<title>페이지</title>
</head>
<body>
<div class="container">
<h1>제목</h1>
<p>본문</p>
</div>
</body>
</html>
// DOM 트리 구조
Document
└── html
├── head
│ └── title → "페이지"
└── body
└── div.container
├── h1 → "제목"
└── p → "본문"
HTML 파싱 중 <script> 태그를 만나면 파싱을 중단하고 스크립트를 다운로드 및 실행합니다. 이를 렌더링 차단이라고 하며, async와 defer 속성으로 최적화할 수 있습니다.
2단계: CSSOM 트리 생성
CSS를 파싱하여 CSSOM(CSS Object Model) 트리를 생성합니다. DOM과 별개로 진행되며, 각 요소의 스타일 규칙을 계산합니다.
/* CSS */
.container { width: 100%; }
h1 { font-size: 24px; color: #333; }
p { line-height: 1.6; }
// CSSOM 트리
StyleSheet
├── .container → { width: 100% }
├── h1 → { font-size: 24px, color: #333 }
└── p → { line-height: 1.6 }
CSS는 렌더링 차단 리소스입니다. CSSOM이 완성되기 전에는 Render Tree를 생성할 수 없으므로, 중요한 CSS는 인라인으로, 나머지는 비동기 로딩하는 것이 좋습니다.
3단계: Render Tree 생성
DOM과 CSSOM을 결합하여 Render Tree를 생성합니다. 화면에 실제로 표시되는 요소만 포함되며, display: none 요소는 제외됩니다.
// DOM + CSSOM → Render Tree
Render Tree
└── html
└── body
└── div.container (width: 100%)
├── h1 (font-size: 24px, color: #333) → "제목"
└── p (line-height: 1.6) → "본문"
// display: none은 제외, visibility: hidden은 포함 (공간 차지)
4단계: Layout (Reflow)
Render Tree의 각 노드가 화면에서 정확히 어디에, 얼마나 큰 크기로 배치될지 계산합니다. 뷰포트 크기에 따라 상대적인 위치와 크기가 결정됩니다.
// Layout 계산 예시
div.container {
x: 0, y: 0
width: 1920px, height: 100px
}
h1 {
x: 16, y: 16
width: 100px, height: 32px
}
Layout은 비용이 큰 연산입니다. 요소의 크기나 위치를 변경하면 영향을 받는 모든 하위 요소의 Layout을 다시 계산해야 합니다.
5단계: Paint
Layout이 완료되면 각 요소를 픽셀로 그립니다. 텍스트, 색상, 그림자, 이미지 등이 실제 화면에 그려집니다.
Paint도 비용이 큰 작업입니다. 특히 그림자, 그라데이션, 보더 둥글기 등은 GPU 가속이 필요할 수 있습니다.
6단계: Composite
여러 개의 레이어로 분리된 요소들을 하나의 화면으로 합성합니다. GPU가 담당하며, transform과 opacity 변경은 Composite 단계만 다시 실행하면 되므로 매우 효율적입니다.
// Composite에 최적화된 속성 (Reflow, Repaint 회피)
.element {
transform: translateX(100px); // Layout X, Paint X, Composite O
opacity: 0.5; // Layout X, Paint X, Composite O
}
// Reflow를 유발하는 속성 (피해야 함)
.element {
width: 100px; // Layout O, Paint O, Composite O
margin-left: 10px; // Layout O, Paint O, Composite O
font-size: 16px; // Layout O, Paint O, Composite O
}
렌더링 최적화 전략
⚡ CSS 최적화
- Critical CSS 인라인: 뷰포트 내 콘텐츠 스타일을 HTML head에 직접 삽입
- CSS 최소화: 사용하지 않는 규칙 제거 (PurgeCSS)
- 선택자 단순화: 복잡한 선택자는 CSSOM 생성을 느리게 함
⚡ JavaScript 최적화
- async/defer: 스크립트가 HTML 파싱을 차단하지 않도록 설정
- 코드 분할: 필요한 스크립트만 먼저 로딩
- requestAnimationFrame: 애니메이션을 브라우저 리프레시 주기에 맞춰 실행
⚡ Reflow 최소화
// ❌ 나쁜 예: 반복적인 Reflow
const boxes = document.querySelectorAll('.box');
for (let i = 0; i < boxes.length; i++) {
const height = boxes[i].offsetHeight; // Reflow 발생
boxes[i].style.height = height * 2 + 'px'; // 또 Reflow
}
// ✅ 좋은 예: 일괄 처리
const boxes = document.querySelectorAll('.box');
const heights = [];
for (let i = 0; i < boxes.length; i++) {
heights.push(boxes[i].offsetHeight);
}
for (let i = 0; i < boxes.length; i++) {
boxes[i].style.height = heights[i] * 2 + 'px';
}
개발자 도구로 분석하기
// Chrome DevTools Performance 탭 사용법
1. Ctrl+Shift+I → Performance 탭
2. Record 버튼 클릭
3. 페이지 새로고침 또는 상호작용
4. Stop 후 분석
// 주요 확인 지표
- Loading: 네트워크 요청 시간
- Scripting: JS 실행 시간
- Rendering: Layout, Paint 시간
- Painting: 실제 픽셀 그리기
- Idle: 유휴 시간
실무 체크리스트
- ✅ Critical CSS를 인라인으로 제공하여 첫 렌더링 차단 최소화
- ✅ JavaScript는 async/defer로 HTML 파싱 차단 방지
- ✅ 이미지는 크기를 명시하여 CLS(Cumulative Layout Shift) 방지
- ✅ 애니메이션은 transform과 opacity만 사용하여 Composite 최적화
- ✅ Reflow를 유발하는 속성 변경은 일괄 처리 또는 requestAnimationFrame 사용
- ✅ 웹 폰트는 font-display: swap으로 FOIT 방지
- ✅ DevTools Performance로 렌더링 병목 지속적으로 모니터링
자주 묻는 질문
Reflow와 Repaint의 차이는 무엇인가요?
Reflow(Layout)는 요소의 위치나 크기가 변경되어 전체 레이아웃을 다시 계산하는 것입니다. Repaint는 위치나 크기는 변하지 않고 색상이나 배경만 변경되어 픽셀을 다시 그리는 것입니다. Reflow가 Repaint보다 훨씬 비용이 큽니다.
will-change 속성을 남발해도 되나요?
아니요. will-change는 GPU 메모리를 소비하므로 꼭 필요한 요소에만 잠시 적용해야 합니다. 애니메이션이 끝나면 제거하세요. 과도한 사용은 오히려 성능을 저하시킵니다.
가상 DOM이 실제 DOM보다 빠른가요?
항상 그렇지는 않습니다. 가상 DOM의 이점은 여러 변경사항을 일괄 처리하여 Reflow 횟수를 줄이는 것입니다. 소규모 변경에서는 직접 DOM 조작이 더 빠를 수 있습니다. React, Vue 등의 프레임워크는 가상 DOM을 통해 개발자가 실수로 Reflow를 반복하지 않도록 보호합니다.
마무리
이번 글에서는 브라우저 렌더링 과정(Critical Rendering Path)을 완벽히 이해하는 방법을 정리해 드렸습니다. DOM 생성부터 Composite까지 각 단계의 역할과 최적화 포인트를 파악하면 웹 성능을 획기적으로 개선할 수 있습니다.
핵심을 정리하면 다음과 같습니다: HTML 파싱을 차단하지 않도록 JS는 비동기 로딩하고, CSS는 Critical 인라인과 비동기 분리로 처리하며, Reflow는 일괄 처리로 최소화하고, 애니메이션은 transform과 opacity로 GPU 가속을 활용하세요.
브라우저 렌더링 과정 관련 궁금한 점이나 문제가 있으시면 댓글로 남겨 주세요. 도움이 되셨다면 이 글을 주변에 공유해 주시는 것도 잊지 마세요!


댓글 0
첫 댓글을 남겨보세요.