FEInterview Prep

browser · high priority

브라우저 렌더링 파이프라인

DOM → CSSOM → Render Tree → Layout → Paint → Composite

intermediate 난이도3시간카카오네이버당근라인배민
시작 전
이해도
매우 낮음

학습 개요

브라우저 렌더링 파이프라인은 서비스가 커질수록 "왜 이 구조를 선택했는가"를 설명해야 하는 순간에 가장 많이 등장합니다. 모든 애니메이션은 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단계

1
DOM 구성

HTML 파싱 → DOM Tree 생성. script 태그를 만나면 파싱이 블로킹됨

1HTMLTokenizeNodeDOM Tree
2
CSSOM 구성

CSS 파싱 → CSSOM Tree 생성. CSS는 항상 렌더링 블로킹 리소스

1CSSTokenizeNodeCSSOM Tree
3
Render Tree 구성

DOM + CSSOM = Render Tree. display:none 요소는 제외됨

4
Layout (Reflow)

각 요소의 위치와 크기 계산. 가장 비싼 연산. 전파(cascade) 발생

5
Paint (Repaint)

각 레이어에 픽셀을 채움. 색상, 텍스트, 이미지 등 시각적 속성

6
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);

비교 분석

브라우저

vs

transform vs top/left 애니메이션

비교 관점
이 방식
transform vs top/left 애니메이션
렌더링 파이프라인
transform: Composite 단계만 (GPU 처리)
top/left: Layout → Paint → Composite (CPU 처리)
성능
transform: 메인 스레드 부하 없음, 60fps 달성 용이
top/left: 매 프레임마다 Reflow 발생, 버벅임 가능
레이어 생성
transform: 자동으로 별도 GPU 레이어 생성
top/left: 별도 레이어 없음
사용성
transform: translate/rotate/scale 사용
top/left: 위치 지정이 직관적

브라우저

vs

defer vs async

비교 관점
이 방식
defer vs async
실행 순서
defer: HTML 문서 순서대로 실행 (순서 보장)
async: 다운로드 완료 순서대로 실행 (순서 불보장)
실행 타이밍
defer: HTML 파싱 완료 후, DOMContentLoaded 전
async: 다운로드 완료 즉시 (HTML 파싱 중단)
적합 스크립트
defer: DOM에 의존하는 스크립트, 다른 스크립트와 의존성 있는 경우
async: Google Analytics 등 독립적인 스크립트

트레이드오프

이상적인 사용 사례

실무 적용

어떤 상황에서 사용하는가

애니메이션이 끊기거나(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 요소가 많을 때 성능 문제가 생기는 이유는?

자기 점검

브라우저 렌더링 파이프라인의 핵심 선택 기준을 한 문장으로 설명해보세요.

기대 키워드

ReflowRepaintCompositetransform

자주 하는 오해

브라우저 렌더링 파이프라인을 단순 정의 암기로만 접근하면 트레이드오프 설명이 약해집니다.

transform이 top/left보다 성능이 좋은 이유를 CRP 단계로 설명해보세요.

기대 키워드

GPUComposite레이어Reflow 없음

학습 자료