
google.com 같은 사이트에 접속할 때 실제로 무슨 일이 벌어지는지를 이해하기 위한 오늘의 주제. 브라우저 단계를 정리해보았다.
[네트워크]
DNS → TCP → HTTPS → index.html 다운로드
↓
[브라우저]
HTML 파싱 → JS 번들 다운로드
↓
[자바스크립트]
React/Vue 앱 실행
↓
[API 네트워크]
백엔드 API 호출
↓
[렌더링]
DOM 생성 → 화면에 UI 표시
DOM: 문서 객체 모델(Document Object Model)의 약자로 HTML/XML 문서의 구조를 프로그래밍 언어가 접근하고 조작할 수 있도록 객체화된 트리(Tree) 구조로 표현하는 표준 인터페이스
HTML
<html>
<body>
<div>안녕</div>
</body>
</html>
DOM
DOM Tree
└─ html
└─ body
└─ div ("안녕")
특징: 파싱은 중간에 멈출 수 있음
< script > 만나면: JS를 먼저 실행해야 해서 HTML 파싱 일시 정지
그래서 요즘엔 defer, async 같은 옵션이 중요
async라는 용어는 비동기 방식으로 코드를 짤때 많이 봤는데 defer은 낯선 용어. 둘의 특징으로는 HTML 파싱을 막지 않고 JS를 다운로드하지만 실행 시점이 다르다
<script src="app.js"></script>
기본적인 script의 동작 순서
1. HTML 파싱 중단
2. JS 다운로드
3. JS 실행
4. 다시 HTML 파싱 재개
-> 문제: JS 크면 화면 표시가 늦어짐 (렌더 블로킹)
"HTML 다 읽고 나서 실행할게"
<script src="app.js" defer></script>
동작 순서
1. HTML 파싱 계속 진행
2. JS는 백그라운드에서 다운로드
3. HTML 파싱 끝남
4. 그 다음 JS 실행 (순서 보장됨)
5. DOM 조작 가능
-> React, 일반 웹앱 거의 다 defer 사용
"다운로드 끝나면 바로 실행"
<script src="app.js" async></script>
동작 순서
1. HTML 파싱 계속
2. JS 다운로드
3. 다운로드 끝나는 즉시 실행(그 순간 HTML 파싱 멈춤)
4. 다시 HTML 파싱
-> 광고, 통계, 추적 스크립트에 많이 사용
기본 script
HTML 파싱 ⛔ → JS 다운 → 실행 → HTML 재개
defer
> HTML 파싱 ────────────── 끝
JS 다운 ────────
JS 실행 ▶
async
HTML 파싱 ────⛔───▶ 재개
JS 다운 ─────▶ 실행
정리
defer는 HTML 파싱이 끝난 후 순서대로 실행되고, async는 다운로드 완료 즉시 실행되어 순서와 DOM 준비가 보장되지 않는다.
CSSOM: 브라우저가 CSS 코드를 이해하고 처리하기 위해 생성하는 트리 형태의 데이터 구조
왜 CSSOM은 '렌더링 차단 리소스'인가?
공부를 하다 보면 CSS는 렌더링을 차단한다는 말을 자주 보게 될 텐데, 그 이유가 바로 CSSOM 때문.
<link rel="stylesheet"> 태그를 HTML의 최상단인 <head> 안에 넣는다 -> 최대한 빨리 CSSOM 생성을 시작하기 위해| 구분 | DOM (Document Object Model) | CSSOM (CSS Object Model) |
|---|---|---|
| 역할 | HTML 문서의 내용과 구조를 객체화 | CSS 스타일 규칙과 상속 관계를 객체화 |
| 생성 시점 | HTML 마크업을 읽는 즉시 점진적으로 생성 | 스타일시트를 모두 로드하고 파싱해야 완료 |
| 구성 요소 | HTML 태그, 텍스트 노드 등 모든 요소 포함 | 스타일 규칙이 적용되는 노드만 포함 |
| 특징 | 화면에 보이지 않는 요소도 포함 | 스타일 상속(Cascading) 규칙이 반영된 트리 구조 |
| 렌더링 영향 | 문서의 뼈대를 제공 | 완료될 때까지 렌더링을 차단 (Render Blocking) |
| 상호작용 | JS로 내용/구조 변경 가능 | JS로 스타일 속성 변경 가능 |
렌더 트리: DOM의 "내용(Contents)"과 CSSOM의 "스타일(Styles)" 정보를 결합하여 만든 최종 트리
특징
<head>, display: none)는 렌더 트리에서 제외된다.순서
1. DOM 트리 순회: <body> 태그부터 시작하여 화면에 표시되는 모든 노드를 하나씩 훑는다.
2. 보이지 않는 노드 제외:
<head>, <script>, <meta> 등)는 무시한다.display: none이 설정된 요소는 렌더 트리에서 완전히 빠진다. (자식 요소들까지 모두 제외됨) visibility: hidden은 공간은 차지하므로 렌더 트리에 포함된다.주의할 점
display: none: 렌더 트리에 아예 포함되지 않음 (공간조차 차지하지 않음)
visibility: hidden: 렌더 트리에 포함. (투명한 박스로 공간은 차지함)
렌더 트리가 만들어졌다고 해서 바로 화면에 그림이 나타나는 것은 아니다?
렌더 트리는 "무엇을 그릴지"는 알지만, "어느 위치에 어떤 크기로" 그릴지는 아직 모르는 상태이기 때문이 정보(좌표, 크기)를 계산하는 다음 단계가 바로 Layout(Reflow).
| 특징 | DOM 트리 | CSSOM 트리 | 렌더 트리 (Render Tree) |
|---|---|---|---|
| 목적 | HTML 구조 파악 | 스타일 규칙 파악 | 실제 화면 구성 요소 파악 |
| 포함 범위 | 모든 HTML 태그 | 모든 CSS 규칙 | 화면에 보이는 요소만 |
| 비표시 요소 | <head>, <script> 포함 | 모든 스타일 포함 | 제외 (예: display: none) |
Layout 단계에서는 뷰포트(Viewport, 브라우저의 화면 크기) 내에서 각 노드가 차지하는 정확한 기하학적 위치를 계산. 이 과정을 다시 실행하는 것을 Reflow라고 함
특징
1. 상대적인 값 → 절대적인 값 (Pixel)
CSS에서 사용한 %, em, rem, vh, vw 같은 상대적인 단위들이 이 단계에서 브라우저 화면에 맞는 절대적인 픽셀(px) 단위로 변환
ex) width: 50% → 브라우저 너비가 1000px이라면 500px로 계산
일반적으로 레이아웃은 문서의 위에서 아래로, 왼쪽에서 오른쪽으로 흐르며 계산. 부모 요소의 크기가 결정되어야 자식 요소의 크기가 결정되는 방식.
언제 Reflow가 발생할까?
화면의 구조가 바뀌는 거의 모든 상황에서 발생. Reflow는 계산량이 많아 성능에 큰 영향을 준다.
- 브라우저 리사이징: 창 크기를 조절할 때 (뷰포트 변화)
- 노드 추가/삭제: DOM 트리에서 요소가 추가되거나 제거될 때
- 요소의 크기/위치 변경: width, height, margin, padding, border, top, left 등 기하학적 속성 변경
- 폰트 변경: 글꼴이 바뀌면 글자의 크기가 달라져 주변 요소의 위치에도 영향을 줌
- 내용 변경: input 창에 텍스트를 입력하여 길이가 늘어나는 경우 등
레이아웃 단계에서 계산된 박스(Box)들을 실제 화면의 픽셀(Pixel)로 변환하는 과정. 텍스트에 색을 입히고, 배경 이미지를 넣고, 테두리를 그리는 등 눈에 보이는 모든 시각적인 요소를 그리는 단계
무엇을 그릴까?
특징
레이어(Layer) 생성 - 브라우저는 효율적인 페인팅을 위해 화면 전체를 한 번에 그리지 않고, 포토샵의 레이어처럼 여러 개의 레이어로 나누어 그림
z-index, position: fixed, video, canvas, 또는 3D 속성이 들어간 요소들은 별도의 레이어로 분리됨Reflow가 발생하면 화면 구조가 바뀌었으므로 무조건 Repaint도 뒤따라 발생. 하지만 구조는 그대로인데 스타일만 바뀌는 경우(예: 글자색 변경)에는 Reflow 없이 Repaint만 발생
성능 팁 : Reflow는 주변 요소까지 다 계산해야 해서 매우 무겁지만, Repaint는 해당 요소만 다시 그리면 되므로 상대적으로 가벼움. 하지만 여전히 CPU/GPU를 소모하므로 잦은 Repaint는 피하는 게 좋다고 한다
Paint 단계에서 분리되어 그려진 레이어들을 스크린에 표시하기 위해 합치는 과정
-> 이 작업은 메인 스레드(Main Thread)가 아닌 별도의 합성 스레드(Compositor Thread)에서 실행되며, GPU(그래픽 카드)를 활용하기 때문에 매우 빠름
Composite 단계가 중요한 이유는 Reflow와 Repaint를 건너뛸 수 있는 유일한 방법이기 때문 -> 특정 속성들을 사용하면 브라우저는 해당 요소를 별도의 레이어로 분리하고 GPU에게 처리를 맡김 이때는 레이아웃을 다시 계산하거나(Reflow) 픽셀을 다시 칠할(Repaint) 필요 없이, 이미 그려진 레이어의 위치나 형태만 변형(Transform)
렌더링 파이프라인 정리
- Reflow 발생: Layout → Paint → Composite (가장 무거움)
- Repaint 발생: Paint → Composite (무거움)
- Composite만 발생: Composite (가장 빠름 / transform 등 사용 시)