웹 페이지 렌더링

웹 페이지가 띄워지는 과정을 가볍게 이해한다.

웹 페이지 렌더링

1. 머릿말

 웹 페이지 방문 시 어떻게 페이지가 생성되는지 알아봅니다.

2. URI 입력

URI를 입력하면 브라우저는 HTTP 요청을 보내고 HTML 문서를 받아온다.

AWS에서 알려주는 Amazon Route 53 DNS 흐름

 URI는 https 등으로 시작하는 주소입니다. 로컬 DNS는 해당 도메인의 캐시가 있으면 바로 응답하되, 처음 보는 URI면 Root 서버 -> 네임 서버 등을 거쳐 매핑되는 IP를 가져옵니다. 결과적으로 해당 서버에 접촉이 성공하면 HTML 파일을 가져옵니다. 이 때의 파일은 HTML, CSS, JavaScript로 구성됩니다.
 참고로 리액트 같은 라이브러리나 기타 프레임워크로 만든 서비스들은 트랜스파일링 등의 작업을 거치기 때문에 사용자에게는 HTML 파일로서 제공됩니다.

3. 파일 분석, 재구성

 파일을 다운로드받으면 아래의 브라우저 구성 요소에 따라 각각 나뉘어져 처리됩니다.

  • UI
  • 렌더링 엔진: HTML, CSS를 처리하고 렌더함
  • 브라우저 엔진: UI, 렌더링 엔진 중개
  • 통신: HTTP 등 네트워크 요청
  • JS 해석기: JS 분석, 실행
  • UI 백엔드
  • 자료 저장소: 쿠키, 캐시 등 저장.
  1. 렌더링 엔진은 렉싱, 파싱하고 DOM, CSSOM트리를 만들고 렌더 트리 합성.
  2. 렌더 트리는 display:none, <head> 처럼 안 보이는 요소는 배제.

3-1. 분석 시점, 방법

<!-- HTML 분석 시작 -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <!-- CSS 분석 시작. 병렬 작업. -->
    <link href="style.css" rel="stylesheet" type="text/css" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div>제목</div>
    <!-- HTML 분석을 멈추고 JS 다운로드 및 분석. -->
    <script src="main.js"></script>
    <!-- HTML 분석 재개 -->
    <div>내용</div>
  </body>
</html>
  • CSS, 이미지 등 자원: 병렬 다운로드.
  • JS: HTML 분석을 멈추고 JS 다운로드.

 자바스크립트는 HTML 파싱을 멈추기 때문에 대표적인 렌더링 방해 자원입니다. 단, 다른 것들도 일단은 렌더링 방해 자원입니다. DOM 트리는 빨리 만들었는데 CSSOM 트리가 늦을 수도 있으니까요.

3-2. 렌더링 방해

 HTML, CSS, JS로 코드 연습을 하다 보면 페이지가 의도와 다르게 나타날 때가 많습니다. 보통은 DOM이 완성되기 전에 DOM 요소에 접근하기 때문입니다.
 그럴 경우엔 DOM이 완성된 다음에 DOM 요소에 접근해야 하는데, 보통 script에 defer 옵션을 붙입니다. 참고로 script 옵션은 두 가지가 있습니다.

  • defer: 다운로드와 HTML 분석을 병행한다. DOM 트리를 만든 후 JS를 실행한다.
  • async: 다운로드와 HTML 분석을 병행한다. 다운로드가 끝나면 바로 JS를 실행하기 때문에 DOM 트리를 만들기 전에 실행할 수도 있다.

 script 문을 코드 마지막 줄에 두는 방법도 있지만, 스크립트 다운로드 시점이 늦어지므로 특별히 사용할 일은 없습니다.
 async는 방문자 수 카운트나 광고 따위의 독립적 시행하는 케이스에 씁니다.

 그럼에도 오류가 날 때도 있는데 defer 옵션은 DOM이 만들어진 이후에 실행하는 것이지, 모든 자원을 다운로드한 다음에 실행하는 게 아니기 때문입니다.
 예를 들면 DOM은 다 구축했지만 이미지, 음악 등의 자원을 다운로드하기 전에 사용하려고 하면 실패할 것입니다. 이러한 자원은 CSS처럼 병렬 다운로드합니다.(다운로드 이후엔 브라우저 캐시에 저장하고 재사용.)

 브라우저는 이미지 등 모든 자원이 준비되면 load 이벤트를 실행합니다. 자원을 쓰려다가 에러가 나면 load 이벤트 때 실행해보면 됩니다.

// DOM 트리가 준비되면 실행
document.addEventListener("DOMContentLoaded", ...);
// 모든 자원이 준비되면 실행
document.addEventListener("load", ...);
// 모든 자원을 unload하고 실행
document.addEventListener("unload", ...);
// 모든 자원을 unload하기 전에 실행
document.addEventListener("beforeUnload", ...);

 이러한 과정을 신경써도, 자바스크립트 용량이 커서 늦게 불러지면 초기 화면과 최종 화면이 상이한 현상이 발생합니다. 이는 서버 사이드 렌더링 방식에서 특히 두드러지며 사용자 경험에 영향을 끼칩니다.
 서버 사이드 렌더링은 서버에서 렌더링한 HTML을 빠르게 가져오고 사용자 브라우저에서 자바스크립트를 이용해 화면을 업데이트하는 방식입니다. 초기 화면과 최종 화면이 조율되는 중간 과정을 하이드레이션이라고 하는데, 긴 하이드레이션은 사용자 경험을 저하시킵니다. (화면은 정상인데 버튼 클릭이 안 되거나, 화면 일부가 덜 렌더링되는 등) 그래서 우리가 보는 페이지들은 번들링, 코드 스플리팅, 캐싱, 서스펜스 등 여러 최적화 기법이 동원됩니다.

4. 조합하고 띄우기

 렌더 트리를 만들면 다음의 과정을 거칩니다.

  1. Layout: 각 요소의 크기, 절대적인 위치, 상대적인 위치 등을 계산.
  2. Paint: 사람 눈에 보이기 시작하는 단계. 각 요소를 레이어로 구분하고 칠한다.
    가장 많은 시간이 소요되는 과정. 레이어로 구분하면 일부 레이어가 변경됐을 때 그 부분만 변경 작업할 수 있다. 예를 들면 포스팅 작성 중에 사진을 변경하면 사진 쪽 레이어만 새로 렌더링된다.
  3. Composite: 각 요소를 순서에 맞춰서 배치하고 z-index 등 겹치는 부분들을 고려해서 조합한다.

 위 과정들은 한 작업을 완전히 마쳐야만 다음 작업을 시작하는 게 아니라 뒤섞여서 진행됩니다. 렌더 트리가 미완성이어도 브라우저는 사용자에게 빠르게 보여주기 위해 불완전한 화면을 띄우면서 렌더링을 작업합니다.

5. 마무리

 웹 페이지는 결국 HTML, CSS, JS로 배포됩니다. 라이브러리, 프레임워크 등은 그러한 코드를 어떻게 쉽고 편하게 만들고 관리할지 고민해서 태어난 것들입니다.

6. 참조