browser · high priority
브라우저 렌더링 파이프라인
DOM → CSSOM → Render Tree → Layout → Paint → Composite
학습 개요
브라우저 렌더링 파이프라인은 서비스가 커질수록 "왜 이 구조를 선택했는가"를 설명해야 하는 순간에 가장 많이 등장합니다. 모든 애니메이션은 transform/opacity로, 스크롤 감지는 IntersectionObserver로, DOM 읽기/쓰기는 분리하여 Layout Thrashing을 방지하는 결정을 설명할 수 있어야 시니어 면접과 실무 설계 리뷰에서 설득력이 생깁니다.
탄생 배경
해결하려 했던 문제
초기 웹은 정적 HTML 문서를 표시하는 것이 전부였습니다. CSS와 JavaScript가 추가되고, 웹 페이지가 애플리케이션화되면서 렌더링 파이프라인이 복잡해졌습니다. JavaScript가 DOM을 동적으로 변경하기 시작하면서 "어떤 순서로, 어떻게 화면을 그리는가"가 성능의 핵심이 되었습니다.
역사적 맥락
1993년 Mosaic 브라우저 등장 → HTML 파싱과 렌더링 개념 도입 → CSS 추가로 CSSOM 개념 등장 → JavaScript 추가로 DOM 동적 조작 가능 → 2000년대 AJAX로 동적 업데이트 증가 → 브라우저 렌더링 최적화 경쟁 → 하드웨어 가속(GPU Compositing) 도입 → 2010년대 60fps 애니메이션이 표준이 됨 → 2020년대 Core Web Vitals로 렌더링 성능이 SEO에 직접 영향
이전에는 어떻게 했나
서버 사이드 렌더링으로 완성된 HTML을 내려받아 브라우저가 그리기만 하던 시절에는 클라이언트 렌더링 최적화가 덜 중요했습니다. JavaScript와 SPA의 등장으로 클라이언트 렌더링이 복잡해지면서 브라우저 렌더링 파이프라인 이해가 필수가 되었습니다.
멘탈 모델
쉬운 설명
복잡한 개념을 실생활 비유로 설명합니다.
“집 짓기”
집(웹페이지)을 짓는 과정을 생각해보세요. 설계도(HTML) 읽기 → 인테리어 계획(CSS) 합치기 → 각 방의 크기와 위치 결정(Layout) → 페인트 칠하기(Paint) → 가구 배치(Composite). Reflow는 방의 크기를 바꾸는 것(비쌈), Repaint는 페인트 색만 바꾸는 것(보통), Composite는 가구를 그냥 옮기는 것(저렴).
핵심 개념
Critical Rendering Path 6단계
DOM 구성
HTML 파싱 → DOM Tree 생성. script 태그를 만나면 파싱이 블로킹됨
1HTML → Tokenize → Node → DOM Tree
CSSOM 구성
CSS 파싱 → CSSOM Tree 생성. CSS는 항상 렌더링 블로킹 리소스
1CSS → Tokenize → Node → CSSOM Tree
Render Tree 구성
DOM + CSSOM = Render Tree. display:none 요소는 제외됨
Layout (Reflow)
각 요소의 위치와 크기 계산. 가장 비싼 연산. 전파(cascade) 발생
Paint (Repaint)
각 레이어에 픽셀을 채움. 색상, 텍스트, 이미지 등 시각적 속성
Composite
여러 레이어를 합성하여 최종 화면 생성. GPU가 처리. 가장 효율적
동작 원리
[전체 렌더링 파이프라인] HTML 수신 → Parsing → DOM 트리 → CSSOM 트리 → Render Tree → Layout → Paint → Composite [1단계: HTML 파싱과 DOM 트리 생성] 브라우저는 HTML을 바이트 스트림으로 수신하고 순차적으로 파싱합니다. Bytes → Characters → Tokens → Nodes → DOM Tree 파싱 중 <script> 태그를 만나면 파싱이 중단됩니다 (렌더링 블로킹). <link rel="stylesheet">를 만나면 CSS 다운로드를 시작합니다. <img>, <video> 등은 비동기로 로드됩니다. [2단계: CSS 파싱과 CSSOM 트리 생성] CSS 파일을 파싱하여 각 선택자의 스타일 규칙을 트리 구조로 저장합니다. CSSOM은 렌더링 블로킹 리소스입니다. CSSOM이 완성되지 않으면 Render Tree를 만들 수 없습니다. 이유: CSS 상속과 계단식(Cascade) 적용을 위해 전체 CSS를 알아야 최종 스타일 결정 가능. [3단계: Render Tree 구성] DOM + CSSOM → Render Tree Render Tree에는 화면에 표시되는 요소만 포함됩니다. display: none 요소는 제외 (visibility: hidden은 포함, 단 그려지지 않음). 각 노드는 최종 계산된 스타일(computed style)을 가집니다. [4단계: Layout (Reflow)] 각 Render Tree 노드의 정확한 위치와 크기를 계산합니다. viewport 크기, %, vw/vh 같은 상대적 단위를 실제 픽셀값으로 변환합니다. 비용이 매우 큰 단계: 특정 DOM/CSS 변경은 이 단계를 다시 트리거합니다. Reflow 유발 속성 (비용 큼): - width, height, margin, padding, border - position, top, left, right, bottom - font-size, font-family - offsetWidth, offsetHeight, getBoundingClientRect() 읽기 (Layout Thrashing) [5단계: Paint] 각 요소를 레이어에 그립니다 (픽셀 채우기). 배경색, 텍스트 색상, 테두리, 그림자 등을 그립니다. Reflow보다는 비용이 작지만 여전히 CPU 작업입니다. Repaint 유발 속성 (Layout 변경 없이): - color, background-color, border-color - box-shadow (일부 경우) - outline [6단계: Composite] 여러 레이어를 합성하여 최종 화면을 구성합니다. GPU에서 실행됩니다 (매우 빠름, 메인 스레드와 독립적). GPU 레이어 생성 조건 (Composite 전용 → 가장 빠름): - transform: translateZ(0) 또는 will-change: transform - opacity 변경 - CSS filter [CSS 애니메이션 최적화 원칙] Reflow 유발 → Repaint 유발 → Composite 전용 순으로 성능 좋음. transform과 opacity는 Composite 단계에서만 처리 → 60fps 달성 가능. top/left 변경은 Reflow → Repaint → Composite 모두 실행 → 느림. [JavaScript의 렌더링 블로킹] JavaScript는 DOM을 수정할 수 있으므로, HTML 파싱을 중단시킵니다. 이유: JS가 아직 생성되지 않은 DOM에 접근하면 에러 발생 가능. 해결책: defer, async, 또는 <body> 끝에 script 배치. defer: HTML 파싱과 병렬로 JS 다운로드, 파싱 완료 후 순서대로 실행. async: HTML 파싱과 병렬로 JS 다운로드, 다운로드 완료 즉시 파싱 중단 후 실행.
핵심 구성 요소
DOM Tree
HTML 파싱 결과물. JavaScript가 조작하는 객체 모델
CSSOM Tree
CSS 파싱 결과물. 스타일 규칙의 계층 구조
Render Tree
DOM + CSSOM 결합. 화면에 그릴 요소와 스타일 정보
Layout (Reflow)
요소의 크기와 위치 계산. 가장 비용이 큰 단계
Paint
픽셀 채우기. 배경, 텍스트, 테두리 등을 레이어에 그림
Composite
GPU에서 레이어 합성. transform/opacity 애니메이션이 여기서 실행
Critical Rendering Path
초기 화면을 그리기까지의 HTML, CSS, JS 처리 과정
흐름 설명
[script defer vs async 동작 차이]
기본 <script src="...">:
HTML 파싱 ──────┐ ┌── 다시 파싱 ──────
│ (파싱 중단) │
└── JS 다운로드 → JS 실행 ┘
<script defer>:
HTML 파싱 ──────────────────────────── 완료
└── JS 다운로드 (병렬) ─── JS 실행 (순서 보장)
<script async>:
HTML 파싱 ──────┐ ┌── 다시 파싱 ──
│ │
└── JS 다운로드 ─── JS 실행 (다운로드 완료 즉시)
[Layout Thrashing 예시]
// 나쁜 패턴: 읽기/쓰기 혼용으로 강제 동기 레이아웃 발생
elements.forEach(el => {
const width = el.offsetWidth; // 읽기: 레이아웃 강제 실행
el.style.width = width + 10 + 'px'; // 쓰기: 다음 읽기 시 재계산 필요
});
// 좋은 패턴: 읽기 → 쓰기 분리
const widths = elements.map(el => el.offsetWidth); // 먼저 모두 읽기
elements.forEach((el, i) => {
el.style.width = widths[i] + 10 + 'px'; // 그 다음 모두 쓰기
});코드 예제
// CSS 애니메이션 최적화: Composite만 사용
// 나쁜 예: top/left 변경 → Reflow → Repaint → Composite
.bad-animation {
position: absolute;
transition: left 0.3s; /* Reflow 유발 → 느림 */
}
/* 좋은 예: transform → Composite 전용 → GPU 가속 */
.good-animation {
transition: transform 0.3s; /* Composite 전용 → 60fps */
}
/* transform: translateX(100px)으로 이동 */
// will-change: 브라우저에게 GPU 레이어 미리 생성 힌트
.animated-element {
will-change: transform, opacity;
/* 주의: 남용하면 GPU 메모리 낭비 */
}
// JavaScript에서 requestAnimationFrame 사용 (Composite 타이밍에 맞춤)
function smoothAnimation() {
requestAnimationFrame(() => {
// 브라우저의 다음 Paint 직전에 실행
element.style.transform = `translateX(${position}px)`;
if (position < targetPosition) {
position += 5;
smoothAnimation(); // 재귀 호출
}
});
}
// IntersectionObserver로 Reflow 없이 가시성 감지
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
// getBoundingClientRect() 같은 Reflow 유발 API 불필요
}
});
}, { threshold: 0.5 });
observer.observe(element);비교 분석
브라우저
vstransform vs top/left 애니메이션
브라우저
vsdefer vs async
트레이드오프
이상적인 사용 사례
실무 적용
어떤 상황에서 사용하는가
애니메이션이 끊기거나(janky), 스크롤 성능이 느릴 때
어떻게 적용하는가
Chrome DevTools Performance 탭으로 Reflow/Repaint 발생 확인 → transform/opacity 기반 애니메이션으로 전환 → will-change 적용 → 레이어 분리
흔한 실수와 안티패턴
- will-change를 모든 요소에 남용하면 메모리 사용량 급증
- 절대 위치(position:absolute) 없이 transform 사용하면 다른 요소 밀어냄
- Layout Thrashing: 루프 안에서 읽기/쓰기를 반복하면 강제 동기 Reflow
면접 질문
답변 방향 힌트
CRP의 6단계 중 어느 단계부터 재실행되는지, GPU 가속과 레이어 분리의 역할을 설명하세요.
반드시 언급할 키워드
- Reflow = Layout 재계산
- Repaint = 픽셀 재채움
- Composite = 레이어 합성
- transform/opacity = Composite만 트리거
- GPU 가속
예상 꼬리 질문
- Layout Thrashing이 무엇이고 어떻게 방지하나요?
- position:fixed 요소가 많을 때 성능 문제가 생기는 이유는?
자기 점검
브라우저 렌더링 파이프라인의 핵심 선택 기준을 한 문장으로 설명해보세요.
기대 키워드
자주 하는 오해
브라우저 렌더링 파이프라인을 단순 정의 암기로만 접근하면 트레이드오프 설명이 약해집니다.
transform이 top/left보다 성능이 좋은 이유를 CRP 단계로 설명해보세요.
기대 키워드