브라우저는 어떻게 웹페이지를 그릴까?
도메인 탐색부터 페인트까지
Table Of Contents
브라우저가 렌더링되는 과정
우리가 웹사이트를 방문할 때, 브라우저는 생각보다 복잡한 과정을 거쳐 화면을 렌더링한다!
브라우저의 렌더링 과정을 잘 아는 것은 사용자 경험과 성능 최적화 측면에서 중요하다.
오늘은 유저가 도메인을 입력할 때부터 시작해서 브라우저가 웹페이지를 어떻게 렌더링하는지 과정을 살펴보자.
1. Navigation (탐색)
사용자가 주소창에 URL을 입력하거나, 링크 클릭, 폼 제출 등의 동작을 통해 요청을 보낼 때마다 탐색이 발생한다.
1.1. DNS Lookup (DNS 조회)
탐색의 첫 단계는 해당 페이지의 자원(assets)들이 어디에 있는지 찾는 것이다.
https://example.com
이라는 URL 주소를 탐색한다고 하면, 브라우저는 DNS 서버에 https://example.com
의 IP 주소를 요청하고, 그 결과로 192.0.2.172
같은 IP 주소를 받아온다.
-
만약 이 주소를 방문한 적이 없다면 DNS lookup(DNS 조회)가 일어난다.
-
DNS 조회가 일어나는 과정은 아래와 같다!
- 브라우저가 DNS Recursive Resolver에게
example.com
URL의 IP 주소를 요청한다.- 캐시에 저장된 IP 주소가 있는지 확인하고, 없으면 아래 순서에 따라서 DNS 서버들에 요청을 보낸다.
- Resolver가 Root Nameserver(루트 네임서버)에 요청을 보낸다.
- Root Nameserver는
.com
을 관리하는 Top Level Domain(TLD) Nameserver(최상위 네임서버)를 알려준다.
- Root Nameserver는
- Resolver가
.com
TLD 네임서버에 요청을 보낸다.- TLD 네임서버는 도메인
example.com
의 도메인의 네임서버 IP 주소를 반환한다.
- TLD 네임서버는 도메인
- 최종적으로 Recursive Resolver가 도메인의 네임서버에 요청을 보낸다.
example.com
의 도메인의 네임서버가example.com
의 IP 주소를 반환한다.
- DNS Recursive Resolver가 브라우저에 IP 주소를 반환한다.
-
-
이렇게 요청된 IP 주소는 캐싱되어 일정 기간동안 DNS 조회 없이 결과를 받아볼 수 있다.
DNS Lookup 과정을 알아보기에 좋은 그림이 있어 첨부해둔다!
(출처: https://www.datadoghq.com/knowledge-center/dns-resolution/)
1.2. TCP handshake
IP 주소를 알게 되면, 브라우저는 서버와 TCP 3-way-handshake를 통해 연결을 수립한다.
- 브라우저는 TCP SYNchronize 패킷을 서버로 보낸다.
- 서버는 SYN을 수신하고, SYN-ACK (SYNchronize-Acknowledge)를 보낸다.
- 브라우저는 서버로 ACKnowledge을 보내고, 서버가 이를 수신하면 TCP 소켓 연결이 설정된다.
2. Response (응답)
서버와 연결이 성립되면 브라우저는 초기 HTTP GET
요청을 보내고 응답 헤더와 함께 대개는 HTML의 내용을 받아온다.
- 이 때, 요청을 보내고 HTML의 첫 패킷을 받는 데 걸린 시간을 TTFB(Time to First Byte)라고 한다.
- 첫 번째 데이터 청크는 일반적으로 14kb 크기이다.
3. Parsing (구문 분석)
브라우저가 첫 번째 데이터 청크를 받으면, 파싱을 시작한다.
- 네트워크를 통해 받은 데이터를 DOM, CSSOM으로 바꾼다.
여기서부터 Critical Rendering Path(중요 렌더링 경로)의 5단계, 즉 브라우저가 HTML, CSS, JavaScript를 화면의 픽셀로 변환하는 단계를 거친다.
3.1. DOM 트리 구축
CRP의 첫 단계는 HTML을 DOM 트리로 만드는 것이다.
-
이 단계에서는 HTML 문서를 시작 및 종료 태그, 속성 이름 및 값을 토큰화하고, 트리가 만들어진다.
- 이 과정에서 이미지같은 non-blocking 자원을 만나면 브라우저는 해당 자원을 요청하고 파싱을 계속한다.
- 만약
async
나defer
속성이 없는<script>
를 만난다면 파싱을 멈추고 해당 스크립트를 분석하게 된다.
-
브라우저가 DOM 트리를 만드는 프로세스는 메인 쓰레드를 차지한다.
- 그동안 프리로드 스캐너는 CSS, JavaScript와 웹폰트처럼 우선순위가 높은 자원들을 요청한다.
defer
와 async
script
HTML 파싱 중 <script>
태그를 만나면 스크립트를 실행하기 위해서 DOM 생성이 멈춘다.
-
이런 경우, 아래 2가지 문제가 발생한다.
- 스크립트에서는 스크립트 아래에 있는 DOM 요소에 접근할 수 없고, DOM 요소에 핸들러를 추가하는 등의 행동이 불가능하다.
- 페이지 위쪽에 용량이 큰 스크립트가 있는 경우, 스크립트가 다운로드될 때까지 스크립트 아래쪽 페이지를 볼 수 없게 된다.
-
이런 문제점을 피하기 위해서, 스크립트를 페이지의 맨 아래에 놓을 수 있다.
<body> ... 스크립트 위 콘텐츠들 ... <script src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script> </body>
하지만 HTML 문서 자체가 아주 큰 경우, 브라우저가 HTML 파싱을 끝낸 다음에 스크립트를 실행받기 때문에 페이지가 느려지게 된다.
defer
속성이 있는 스크립트는 백그라운드에서 다운로드된다.
- defer 스크립트를 다운로드 하는 도중에도 HTML 파싱은 멈추지 않는다.
- defer 스크립트의 실행은 페이지 구성이 끝날 때까지 지연되고,
DOMContentLoaded
이벤트 발생 전에 실행된다. - defer 스크립트는 HTML에 추가된 순으로 실행된다.
defer
속성은 외부 스크립트(scr가 있어야 함)에만 유효하다.
async
async
속성이 붙은 스크립트는 페이지와 완전히 독립적으로 동작한다.
- async 스크립트 역시 백그라운드에서 다운로드된다.
- HTML 페이지는 async 스크립트가 다운로드되는 것을 기다리지 않고 페이지 내에서 콘텐츠를 처리한다.
- async 스크립트 실행 중에는 HTML 파싱이 멈춘다.
DOMContentLoaded
이벤트와도 서로를 기다리지 않는다.- 페이지 구성이 끝난 후 async 스크립트 다운로드가 끝난 경우,
DOMContentLoaded
이벤트는 async 스크립트 실행 후에 발생할 수 있다. - 페이지 구성이 끝나기 전에 async 스크립트의 다운로드가 끝난 경우,
DOMContentLoaded
이벤트는 async 스크립트 실행 전에 발생할 수 있다.
- 페이지 구성이 끝난 후 async 스크립트 다운로드가 끝난 경우,
- 다른 스크립트들과도 서로를 기다리지 않는다.
- 결과적으로 async 스크립트들은 실행 순서가 제각각이 된다.
- async 스크립트는 방문자 수 카운터나 광고 관련 스크립트처럼 각각 독립적인 역할을 하는 서드 파티 스크립트를 통합할 때 유용하다.
3.2. CSSOM 트리 구축
CRP의 두 번째 단계는 CSS를 처리해 CSSOM 트리로 만드는 것이다.
DOM이 페이지의 모든 내용을 포함한다면, CSSOM은 DOM을 스타일링하기 위한 페이지의 모든 스타일 정보를 포함한다.
- CSS 규칙은 아래로 종속되어, 이전 규칙들을 덮어쓸 수 있기 때문에 CSSOM이 완전히 만들어지기 전까지는 렌더 트리를 생성하지 않는다.
- 일반적으로 CSS를 처리하는 시간은 매우 빠르기 때문에 CSSOM 트리 구축 과정에서 최적화의 여지는 거의 없다.
4. Render
렌더링 과정에는 스타일, 레이아웃, 페인트가 포함되고, 때로는 합성까지 포함할 때가 있다.
4.1. Style
CRP의 세 번째 단계는 DOM과 CSSOM을 합쳐 Render Tree를 만드는 것이다.
- 이 과정에서는 보이는 컨텐츠만 포함된다.
- (일반적으로)
<head>
요소와 그 자식들은 보이는 정보가 없으므로 렌더 트리 안에 포함되지 않는다. <script>
태그 역시 화면에 표시되지 않기 때문에 렌더 트리에 포함되지 않는다.display: none
처럼 화면에 나타나지 않는 태그의 경우 렌더 트리에 포함되지 않는다.visibility: hidden
속성을 가진 요소는 자리를 차지하기 때문에 렌더 트리에 포함된다.
- (일반적으로)
4.2. Layout
CRP의 네 번째 단계는 렌더 트리를 기반으로 각 노드의 크기와 위치를 계산하는 과정이다.
-
각 요소들의 정확한 크기와 위치를 결정하기 위해서, 브라우저는 렌더 트리의 루트부터 시작해 트리의 전체를 순회한다.
-
처음 각 요소들의 사이즈와 위치가 결정되는 과정을 Layout(레이아웃) 이라고 한다.
-
레이아웃 이후, 페이지 전체나 일부에 대한 위치를 결정하는 과정은 Reflow(리플로우) 라고 한다.
4.3. Paint
CRP의 다섯 번째 단계는 각 노드를 화면에 그리는 것이다.
-
페인트 단계에서, 브라우저는 레이아웃 단계에서 계산된 각 박스를 실제 화면의 픽셀로 변환한다.
-
페이지를 렌더링할 때, 요소들을 하나의 레이어에 그리지 않고 여러 개의 레이어로 나누면 페인트 및 리페인트의 성능을 향상시킬 수 있다.
- 레이어를 생성하는 요소 및 CSS 속성으로는
<video>
,<canvas>
태그opacity
transform: translate3d()
,transform: scale3d()
,transform: rotate3d()
- 등이 있다.
-
페인팅이 처음 일어나는 시점을 First Meaningful Paint(FMP, 최초 의미있는 페인트)라고 한다.
4.4. Compositing (합성)
문서의 각 부분이 다른 레이어에서 그려질 때, 각 부분들이 올바른 순서로 화면에 그려지기 위해서는 합성 단계가 필요하다.
자체 레이어에서 렌더링되는 속성은 합성 과정에서만 업데이트되어 리페인트가 트리거되지 않는다.
위 과정을 간략화시키면 아래 그림과 같다.
(출처: https://dev.to/gopal1996/understanding-reflow-and-repaint-in-the-browser-1jbg)
CSS 속성의 비용
- 요소의 크기와 위치에 영향을 주는 속성들은
- 스타일 재계산
- 레이아웃(리플로우)
- 페인트(리페인트)를 수행한다.
- ex)
left
,max-width
,border-width
,margin-left,
font-size
- 요소의 크기와 위치에 영향을 주지 않고, 자체 레이어에서 렌더링되지 않는 속성들은 레이아웃 과정을 필요로 하지 않고,
- 스타일 재계산
- 페인트(리페인트)를 수행한다.
- ex)
color
- 자체 레이어에서 렌더링되는 속성은 composition 과정에서만 업데이트되기 때문에 리페인트 과정도 필요하지 않다.
- ex)
transform
,opacity
- ex)
참고
- Reflow: https://developer.mozilla.org/en-US/docs/Glossary/Reflow
- Repaint: https://developer.mozilla.org/en-US/docs/Glossary/Repaint
- Understanding Reflow and Repaint in the browser: https://dev.to/gopal1996/understanding-reflow-and-repaint-in-the-browser-1jbg
- Populating the page: how browsers work: https://developer.mozilla.org/en-US/docs/Web/Performance/Guides/How_browsers_work
- How the web works: https://developer.mozilla.org/en-US/docs/Learn_web_development/Getting_started/Web_standards/How_the_web_works
- How browsers load websites: https://developer.mozilla.org/en-US/docs/Learn_web_development/Getting_started/Web_standards/How_browsers_load_websites
- What is DNS? | How DNS works: https://www.cloudflare.com/learning/dns/what-is-dns/?utm_source=chatgpt.com
- Critical rendering path: https://developer.mozilla.org/en-US/docs/Web/Performance/Guides/Critical_rendering_path
- Animation performance and frame rate: https://developer.mozilla.org/en-US/docs/Web/Performance/Guides/Animation_performance_and_frame_rate
- defer, async 스크립트: https://ko.javascript.info/script-async-defer
여담
reflow repaint는 브라우저의 렌더링 성능과 연관되어 있기 때문에 FE 개발자에게는 중요한 개념이라고 생각한다.
나는 면접에서 2번이나 마주쳤음에도 불구하고 숙지하지 않았었고 결국...
포항항ꉂꉂ(ᵔᗜᵔ*)
하지만 3번째는 실수하지 않아