브라우저가 받은 HTML·CSS·JS를 화면의 픽셀로 바꾸는 순서. 처음 로드할 때도, 이후 무언가 바뀔 때도 같은 단계를 거친다. 바뀐 내용에 따라 어느 단계부터 다시 시작하는지만 다르다.
단계
- 파싱(Parsing): HTML을 위에서부터 읽어 DOM(Document Object Model) 트리를, CSS를 읽어 CSSOM(CSS Object Model) 트리를 만든다. 동기
<script>를 만나면 파싱이 멈추고, CSS는 렌더링을 막는 리소스다 - 스타일 계산(Style): DOM과 CSSOM을 합쳐 노드마다 최종 스타일을 정한다(캐스케이드가 여기서 적용된다)
- 렌더 트리(Render Tree): 실제로 그릴 노드만 모은다.
display: none은 빠지고,visibility: hidden은 자리를 차지하므로 남는다 - 레이아웃(Layout): 노드마다 크기와 위치를 계산한다. 다시 계산하는 것을 리플로우라고 부른다
- 페인트(Paint): 글자·색·테두리·그림자를 픽셀로 칠한다. 다시 칠하는 것을 리페인트라고 부른다
- 합성(Compositing): 여러 레이어를 순서대로 겹쳐 최종 프레임을 만든다. 주로 GPU(Graphics Processing Unit)가 맡는다 → 합성 단계와 transform·opacity
flowchart TD H[HTML] -->|파싱| D[DOM] C[CSS] -->|파싱| O[CSSOM] D --> S[스타일 계산] O --> S S --> R["렌더 트리 (그릴 노드만)"] R --> L["레이아웃 (크기·위치)"] L --> P["페인트 (픽셀 칠하기)"] P --> X["합성 (레이어 겹치기)"]
한 프레임은 대략 JS → 스타일 → 레이아웃 → 페인트 → 합성 순으로 흐르고, 60Hz 화면이라면 이 모든 과정이 약 16.7ms 안에 끝나야 끊기지 않는다. 화면을 다시 그릴 기회는 이벤트 루프의 렌더링 단계에서 오고, requestAnimationFrame 콜백은 그 직전에 실행된다.
자주 헷갈리는 점
- 서버에서 렌더링(SSR, Server-Side Rendering)하거나 서버 컴포넌트(React Server Components)를 써도 서버가 보내는 것은 클래스가 붙은 HTML과 CSS 파일뿐이다. 스타일 계산·레이아웃·페인트는 언제나 브라우저가 한다. Tailwind CSS가 "서버용 CSS"가 아닌 이유도 같다
- SPA(Single Page Application)는 처음에 빈 HTML을 받고 JS가 DOM을 만든 뒤에야 이 파이프라인이 의미 있는 화면을 그린다. React는 Virtual DOM과 재조정으로 실제 DOM 변경을 줄이는데, 결국 이 파이프라인을 다시 도는 횟수를 줄이는 일이다
- 레이아웃 값을 읽는 코드가 끼어들면 브라우저가 단계를 앞당겨 실행한다 → 레이아웃 스래싱
출처: MDN — How browsers work: Render · MDN — Critical rendering path