본문 바로가기

Web

브라우저 렌더링 작동 원리: 파싱부터 렌더링까지

중요한 렌더링 경로 다섯 단계

DOM 트리 구축 - CSSOM 구축 - 렌더 트리 생성 - 레이아웃 계산 - 페인트


구문 분석(Parsing)

 

- 프로그래밍 언어의 문법에 맞게 작성된 텍스트 문서를 읽고 실행하기 위해

텍스트 문자의 문자열을 분해하고 구조를 생성하는 일련의 과정

 

- 브라우저가 받은 HTML, CSS, JavaScript 코드를 이해하고 화면에 표시할 준비를 하는 것

1) HTML->DOM 생성

2) CSS -> CSSOM 생성

3) JavaScript -> 필요한 경우 DOM/CSSOM을 변경

이 과정이 끝나야 실제로 화면에 콘텐츠가 보이기 시작

 

더보기

- TTFB(Time To First Byte)

웹페이지를 클릭하면 서버에 요청하고, 서버가 응답을 주기까지 시간이 걸림.

 

"TTFB란?"

요청을 보낸 순간부터 첫 번째 데이터(HTML 조각)을 받을 때까지 걸린 시간

 

즉, 서버가 얼마나 빨리 응답하는지를 측정하는 중요한 지표

  • TTFB가 길면, 웹사이트가 느려보일 수 있음.
  • TTFB가 짧으면, 빠르게 페이지 로딩이 시작됨.

- 브라우저는 첫 14KB 데이터를 받으면 바로 렌더링 시작!

  • 서버에서 HTML 파일을 보낼때, 처음에는 14KB정도의 작은 청크(조각)을 보낸다.
  • 이 작은 데이터를 받으면, 브라우저는 "아, 이제 페이지를 그려야겠구나"하면서 화면 준비
  • HTML이 안 와도, 받은 만큼 먼저 처리(구문분석)하면서 렌더링을 시작할 수 있음.

즉, 웹페이지 로딩을 빠르게 하려면

중요한 HTML, CSS를 이 첫 14KB 안에 넣어야함! (페이지 템플릿, 주요 스타일 등)

 

- DOM 트리 구축

HTML을 토큰화해서 분석후, DOM 트리 생성.

DOM 노드의 개수가 많을 수록 DOM 트리를 만드는데 시간이 오래 걸림

 

JavaScript(<script>)를 만나게되면 HTML 분석을 멈추고, 다운로드&실행 후 분석 재개.

JavaScript는 HTML을 수정할 수도 있기 때문!

 

 

✅ 해결책 => async & defer

 

  • async

    - 스크립트를 백그라운드에서 다운받으면서 HTML 파싱
    - 다운로드가 끝나는 순간 HTML 파싱이 멈추고, 스크립트 실행
      -> HTML 문서가 완전히 다운로드되지 않은 상태에서 스크립트 실행 가능성 있음
    - 여러개의 async 스크립트는 순서가 보장되지 않음. 먼저 다운로드된 순서대로 실행
       -> 방문자수 카운터, 광고 스크립트처럼 독립적인 스크립트, 혹은 실행순서가 중요하지 않은 경우 유용
    <script src="app.js" async></script>

 

  • defer

    -  async와 마찬가지로 HTML 파싱 도중에 스크립트를 백그라운드에서 다운하고,
    HTML 파싱이 끝난 후 스크립트 실행(DOMContentLoaded 이벤트 발생 전에 실행)
    - 여러개의 defer 스크립트는 문서에 작성된 순서대로 실행
    - DOM 전체가 필요한 스크립트나, 실행순서가 중요한 경우 유용
    <script src="app.js" defer></script>

 

- 프리로드 스캐너(Preload Scanner)

 

프리로드 스캐너는 HTML을 다 분석하기 전에 미리 중요한 자원들(이미지, CSS, JS)을 찾아서 다운로드 함

즉, 프리로드 스캐너는 HTML 구문 분석과는 별도로 "필요한 자원을 미리 찾아서" 다운로드하는 역할을 함.

 

메인 쓰레드가 HTML과 CSS를 분석하고 있을 때, 프리로드 스캐너는 스크립트와 이미지를 찾아 다운로드하기 시작할 것입니다. Javascript의 분석과 실행 순서가 중요하지 않고 스크립트가 프로세스를 막지 않도록 하려면 async 속성이나 defer 속성을 추가하세요.

 

문서에서 이 부분이 이해가 안 됐다.

async와 defer가 미리 JS 파일을 다운한다고 했는데, 그러면 프리로드 스캐너와 역할에 큰 차이가 없는 것 아닌가?

 

그에 대한 답변은 GPT를 참고했다.

  • 브라우저가 HTML을 읽으면서 <script> 태그를 만나야 원래는 JS 다운로드를 시작할 수 있음.
  • 하지만 프리로드 스캐너는 HTML을 다 읽기 전에 <script>를 찾아서 미리 다운로드 요청을 보냄!
  • 즉, async나 defer가 있어도 "다운로드 시작을 앞당기기 위해" 프리로드 스캐너가 필요함.

-> 그러니깐 프리로드 스캐너의 역할은 다운로드를 미리 요청하는 것이었다.

따라서 async/defer와 함께 쓰면 다운로드를 더 빨리 시작할 수 있다.

 

-> async/defer가 없다면, 어차피 스크립트가 다운로드시 HTML 파싱이 멈추기 때문에 프리로드 스캐너가 큰 의미가 없어진다. 따라서 스크립트 다운으로 인한 블로킹 현상을 막고싶다면 async/defer를 사용하도록 하자.

 

- CSSOM 구축

 

CSSOM을 만드는데 드는 시간은 일반적으로 한 번의 DNS 조회를 하는 시간보다 짧기 때문에 웹 성능 최적화의 관점에서 CSSOM는 성능 향상에 큰 기여를 할 수 있는 영역은 아니다.

 


렌더(Render)

 

1) 렌더 트리 생성

 

DOM과 CSSOM이 생성되면 이 둘은 결합하여 Render Tree를 생성한다.

렌더 트리는 CSS 스타일이 적용된  DOM 트리라고 생각하면 된다.

 

렌더 트리는 모든 HTML 요소가 포함된 DOM 트리와 달리, 브라우저 화면에 렌더링되는 노드만으로 구성된다.

따라서 불필요한 레이아웃 계산을 줄이고 렌더링 성능을 최적화한다.

 

HTML meta 태그, CSS의 display: none 처럼 브라우저 화면에 보여지지 않는 것들은 포함하지 않는다.

하지만 visible: hidden은 보이지는 않지만 공간을 차지하기 때문에 포함한다.

 

 

 

2) 레이아웃(Layout) 계산

 

 

  • 렌더 트리를 기반으로 각 요소의 크기와 위치를 계산
  • 브라우저 창 크기, 글꼴 크기, 뷰포트 크기 등을 고려하여 픽셀 단위의 정확한 위치를 결정.
  • 예를 들어, width: 50% 가 설정된 요소는 부모 요소의 크기를 계산한 후 배치됨.

** 추가 공부 사항: 리플로우(reflow)와 리페인트(repaint)

 

3) 페인트(Paint)

 

 

  • 레이아웃이 완료되면 각 요소의 색상, 그림자, 텍스트 등의 시각적 스타일을 픽셀로 변환.
  • 브라우저는 CPU를 사용하여 픽셀 데이터를 생성(→ GPU에 전달).
  • 이미지, 배경색, 텍스트 색상 등 시각적 요소를 그리는 과정.

 

4) 합성(Composition)

  • 일부 요소는 자신만의 독립적인 레이어(layer) 를 가질 수 있음.
  • position: fixed;, transform: translate(), will-change, opacity 등이 적용된 요소는 GPU 레이어로 분리됨.
  • GPU가 독립적인 레이어를 합성(Composite)하여 최종 화면을 출력.
  • 이 단계에서 최적화가 가능!
    • 예: transform: translateZ(0); 을 사용하면 GPU 가속을 유도하여 렌더링 속도를 높일 수 있음.

 

브라우저는 모든 요소를 한번에 그리는 것이 아니라,

필요한 요소들을 그룹으로 묶어 레이아웃을 만들고, 이 레이어들을 조합하여 화면을 그린다.

 

레이어가 따로 있으면 한 부분이 바뀌어도 전체를 다시 그리지 않고, 해당 레이어만 갱신하면 된다. -> 성능 향상

GPU가 독립적인 레이어를 빠르게 합성(Composite)하여 부드러운 렌더링 가능.

하지만 너무 많은 레이어를 만들면 메모리 사용량이 증가하여 성능이 떨어질 수도 있다.

 

 

 

<자동으로 레이어가 되는 요소>

  • <video> 태그
  • <canvas> 태그
  • CSS 속성 중 position: fixed; 가 적용된 요소

 

적절한 레이어 사용 예시

  • 큰 이미지 배경 (background-image)
  • 애니메이션이 있는 요소 (transform, opacity 변경)
  • 고정된 UI 요소 (position: fixed)

불필요한 레이어 사용 예시

  • 작은 아이콘 하나하나를 전부 레이어로 만들기
  • 스크롤하지 않는 정적인 요소들까지 레이어화