Web
웹은 어떤 과정으로 화면을 보여주는 걸까?
서론
웹은 어떻게 브라우저를 통해 사용자에게 보여질 수 있는 걸까?
이 과정은 아래와 같이 크게 두 단계로 나눌 수 있다.
- 웹 리소스를 받아오는 과정
- 받아온 리소스를 브라우저 화면에 그리는 과정
웹 개발자라면 꼭 알아야 할 중요 개념이다. 특히 받아온 리소스를 브라우저 화면에 그리는 과정은 사실상 프론트엔드 개발자가 작업한 내용의 대부분이 이루어지는 토대라고 할 수 있다.
항상 명확한 일련의 순서를 기억하고 있지 못하고 과정 중 일부를 눈으로 보고서야 ‘그래 이런 과정을 거쳤었지’하는 정도로 얕게 알고 있었는데, 다시 한 번 정리하면서 놓쳤던 부분을 생각하는 시간을 가지려고 한다.
웹 리소스를 받아오는 과정
먼저 브라우저는 어떤 과정을 통해서 웹 서버에서 데이터를 가져오는 걸까? 웹 브라우저에 주소창을 입력하면 어떤 일이 일어나는 지 알아보자.
알면 좋은 기본 지식들
DNS
는 도메인 이름 시스템을 말한다. 웹 서버 또한 컴퓨터를 식별할 수 있는 고유 IP(Internet Protocol) 주소로 되어 있기 때문에 이를 쉽게 기억할 수 있게 전화번호부의 이름을 사용한다. 우리가 흔히 사용하는 URL 주소(khakidiggin-log.vercel.app)를 표현하는 방식을 말하며, 이 주소를 도메인(domain)이라고 한다.
Protocol
은 통신을 위한 규약을 말한다. 대부분의 웹사이트 앞에 붙는https://
는 Hypertext Transfer Protocol (Secure)의 줄임말이고, 브라우저에 전송 계층 보안을 사용하여 서버에 연결하라고 지시한다. 처음 웹 개발을 배웠을 때 무료 호스팅 서비스에 FTP라는 또 다른 프로토콜을 통해서 내 HTML 파일을 전송했던 기억이 나는데,ftp://
,mailto://
,file://
또한 브라우저가 처리할 수 있는 또 다른 프로토콜이다.
TCP/IP
는 데이터 전송을 위한 프로토콜이다. 데이터 전송의 신뢰성을 보장하는 TCP와 데이터 패킷을 목적지로 전달하는 IP로 구성되어 있다.- TCP(Transmission Control Protocol)는 데이터를 작은 조각(패킷)으로 나누어 전달하는데, 퍼즐 조각을 주고받아서 퍼즐을 완성하는 것처럼 데이터를 조립한다. IP(Internet Protocol)은 데이터의 출발지와 목적지의 주소를 포함시켜 네트워크를 통해 데이터를 전달한다.
- 주로 TCP가 데이터 패킷을 분할하고, IP가 각 패킷에 주소를 붙이고 네트워크를 통해 패킷을 전송한 뒤, 목적지에서 TCP가 패킷을 재조립하여 데이터를 복원한다.
CDN
은 콘텐츠를 사용자에게 빠르게 전달하기 위한 분산 서버 네트워크다. 세계 여러 위치에 서버를 두어서 사용자가 요청한 콘텐츠를 사용자의 위치에서 가까운 서버에서 보낼 수 있게 한다.
TLS
는 인터넷을 통한 통신을 암호화하는 보안 프로토콜로, HTTPS의 S(Secure)는 TLS를 통해 통신을 암호화하고 있다는 뜻이다. HTTPS를 사용하면 브라우저와 서버 간 교환되는 데이터 중 암호화가 필요한 패스워드나 신용 카드 정보와 같은 민감한 정보를 보호할 수 있다.- SSL/TLS 또는 SSL이라고도 한다. SSL의 버전 3.0 이후 이름을 TLS로 바꾸었으나, SSL이라는 이름에 사람들이 익숙하다 보니 대다수의 보안 프로토콜이 TLS로 교체되었음에도 SSL이라고 부르고 있는 상황이다.
주소창에 URL을 입력하면 무슨일이?
개발자 면접 인터뷰에 나오는 단골 질문이기도 한 “주소창에 URL을 입력하면 어떤 일이 일어나나요?”의 답을 살펴보자.
사실 이 질문에 대해 깊이 있는 답변을 하기 위해서는 웹 브라우저와 운영 체제, ISP, 호스팅 서버, 서버에서 실행되는 서비스에 대한 지식이 필요하다. 이 질문에 대한 답은 AWS 블로그에 상세히 기술되어 있다. 긴 글을 짧게 요약하면 다음과 같은 과정을 거친다.
브라우저 주소창에 URL을 입력하면 아래와 같은 과정을 거친다.
- DNS 조회: 브라우저가 도메인 이름을 IP 주소로 변환
- TCP 연결: IP 주소를 통해 서버와 TCP 연결을 설정
- HTTP 요청: 브라우저가 서버로 HTTP 요청을 전송
- 서버 응답: 서버가 요청을 처리하고 응답을 돌려줌
- 콘텐츠 렌더링: 브라우저가 서버로부터 받은 데이터를 화면에 렌더링
- 브라우저는 주소창에 URL이 입력되면 인터넷에서 연결할 서버를 파악한다. 웹 사이트를 호스팅하고 있는 웹 서버의 IP 주소를 조회해야 하는데, 이 과정을 DNS 조회라고 한다. 전화번호부에 이름을 검색해서 전화번호를 찾는 작업이다. 과정의 결과로 웹 서버의 IP 주소를 받는다.
- 그 다음 이 웹 서버의 IP 주소를 통해 TCP 연결을 설정한다. IP 주소를 통해서 브라우저는 웹 서버에 직접 연결하지 않고 주로 CDN을 통해 사용자 위치에서 가까운 콘텐츠 배포 서버에 연결한다.
- 연결이 설정되면 HTTP 또는 HTTPS를 통해 통신을 시작한다. (HTTPS를 사용하는 경우에는 TLS 핸드셰이크라는 과정이 추가된다.) HTTP 요청에는 시작 줄(start line), 헤더(headers), 본문(body)이 포함된다. 보통 GET 요청에서는 본문이 비어있다.
- 웹 서버가 HTTP 요청을 받고 나면 받은 요청의 시작 줄, 헤더, 본문의 정보를 기반으로 처리 방법을 결정하고 응답을 보낸다. 응답에는 마찬가지로 상태 줄(status line), 헤더(headers), 본문(body)가 포함된다.
HTTP/1.1 200 OK Date: Tue, 25 May 2021 19:40:59 GMT Server: Apache X-Frame-Options: SAMEORIGIN X-Powered-By: Express Cache-Control: max-age=0, no-cache Content-Type: text/html; charset=utf-8 Vary: Accept-Encoding X-Mod-Pagespeed: 1.13.35.2-0 Content-Encoding: br Keep-Alive: timeout=1, max=100 Connection: Keep-Alive Transfer-Encoding: chunked <!DOCTYPE html> <html lang="ko"> .. html 텍스트
- 웹 브라우저는 응답 HTTP 헤더를 검사하여 받은 리소스를 어떻게 처리해야 할 지 확인한다. Content-Type 헤더가 브라우저 응답 본문에서 HTML 리소스를 수신했다는 것을 확인하면, 이제 브라우저는 받은 리소스로 화면을 그리는 과정을 시작한다.
받아온 리소스로 화면을 그리는 과정
웹 브라우저는 HTML을 검사한다. HTML 내에서 CSS나 Javascript 파일 리소스를 참조하고 있다면, 웹 브라우저는 이 리소스를 가져오도록 서버에 후속 요청을 한다. 이미지 파일이 필요하다면 또 요청한다. 이렇게 HTML, CSS, JS 등 웹 사이트를 구성하는 리소스를 모두 요청하는 과정과 함께 받아온 리소스를 통해 웹 브라우저의 화면 그리기가 시작된다.
알면 좋은 기본 지식들
DOM
은 Document Object Model의 줄임말이다. 한국어로 문서 객체 모델이라고 하는데, 메모리에 웹 페이지 문서 구조를 표현함으로써 스크립트 및 프로그래밍 언어와 페이지를 연결한다. 이때 스크립트는 주로 Javascript를 의미하지만 HTML, SVG, XML 객체를 문서로 모델링하는 것은 Javascript 언어의 일부가 아니다. 문서를 논리 트리로 표현하며, 트리의 각 브랜치는 노드에서 끝나며 각 노드는 객체를 갖는다.
CSSOM
은 CSS Object Model의 줄임말이다. Javascript에서 CSS를 조작할 수 있는 API의 집합이다. HTML 대신 CSS가 대상인 DOM이라고 생각할 수 있으며, 사용자가 CSS 스타일을 동적으로 읽고 수정할 수 있는 방법이다.
브라우저의 점진적 렌더링 (Progressive Rendering)
브라우저는 웹 페이지 렌더링을 한 번에 전부 수행하지 않는다.
최대한 빠르게 웹 페이지를 표시하기 위해 항상 부분적으로, 점진적으로 렌더링을 수행하여 제공한다. 응답으로 받은 리소스인 HTML 문서를 읽고 구조를 파악(파싱 과정, Parsing)하는 것을 처음으로 한다. 이후에는 부분적으로 중요한 콘텐츠부터 렌더링하고 추가적인 콘텐츠인 나머지 CSS, 이미지, Javascript를 점차 불러와서 붙여주는 과정을 수행한다.
크리티컬 렌더링 패스(Critical Rendering Path)
한국어로 중요 렌더링 경로라고 번역되는 크리티컬 렌더링 패스(Critical Rendering Path, 이하 CRP)는 사실상 프론트엔드 개발자가 구현한 대부분의 작업이 수행되고 있는 토대라고 할 수 있다. CRP는 웹 페이지가 브라우저에 로드되고 화면에 렌더링되는 과정을 말한다.
웹 개발자 면접 과정에서 질문으로 자주 나오는 “브라우저의 렌더링 과정을 설명해보세요.”라는 질문이 곧 CRP라는 프로세스를 물어보는 것이라고 할 수 있다.
‘중요 렌더링 경로’라는 한국어 번역체는 약간 와닿지 않는 것 같다. ‘핵심 렌더링 경로’, ‘주요 렌더링 경로’, ‘핵심 표현 과정’, ‘핵심 그리기 과정’ 등의 표현으로도 이해할 수 있다.
사실상 표준 문서라고 할 수 있는 MDN Web Docs에서 개념 학습을 하는 것이 좋다고 배웠다. MDN에서 CRP를 어떻게 설명하고 있을까? 한 줄을 꼽자면 아래와 같다.
중요 렌더링 경로(CRP)는 HTML, CSS, JavaScript를 화면에 픽셀로 변환하기 위해 브라우저가 거치는 일련의 단계를 말하며, CRP를 최적화하면 렌더링 성능이 향상됩니다. 중요 렌더링 경로에는 DOM, CSSOM, 렌더 트리 그리고 레이아웃이 포함됩니다.
The Critical Rendering Path is the sequence of steps the browser goes through to convert the HTML, CSS, and JavaScript into pixels on the screen. Optimizing the critical render path improves render performance. The critical rendering path includes the Document Object Model (DOM), CSS Object Model (CSSOM), render tree and layout. - mdn
“HTML, CSS, JavaScript를 화면에 픽셀로 변환하기 위해 브라우저가 거치는 일련의 단계”를 크리티컬 렌더링 패스(CRP)라고 한다. 과연 프론트엔드 개발자가 모르면 안되는 근본, 뿌리 그 자체다. 브라우저 렌더링의 모든 과정에서 어떤 알고리즘과 코드를 사용하고 있는지 꿰고 있지는 않더라도, 내가 개발한 프로그램을 실행시켜 주는 브라우저가 어떤 순서로 작동해서 내 프로그램을 보여주고 있는지 정도는 알아야 하지 않을까?
브라우저가 화면에 웹을 그리는 과정
브라우저가 화면에 웹을 렌더링하는 과정은 아래와 같이 구분할 수 있다.
- HTML 파싱 및 DOM 생성
- CSSOM 생성 및 Javascript 실행
- 렌더 트리 생성 및 레이아웃 계산
- 페인팅 및 컴포지팅
DOM과 CSSOM 생성
HTML을 읽어서 DOM 트리를 만들고, CSS를 읽어서 CSSOM 트리를 만들고, Javascript가 DOM과 CSSOM에 영향을 미치는 경우를 고려하여 Javascript를 실행한다.
렌더 트리(Render Tree)
렌더 트리는 화면에 표시될 요소들을 포함하는 트리 구조를 말한다. DOM 트리와 CSSOM 트리를 결합한 결과로 생성되고, 실제로 화면에 렌더링 되어야 하는 요소만 선별해서 트리를 생성한다.
display: none
이 적용되면 해당 요소는 레이아웃 트리에 포함되지 않는다. 하지만 visibility: hideen
이 적용된 요소는 레이아웃 트리에 포함된다. visibility: hidden은 요소를 보이지 않게 하지만 여전히 레이아웃에서 빈 상자라는 공간을 차지한다. 반면 display:hidden은 요소가 보이지 않고 레이아웃에 포함되지 않도록 렌더링 트리에서 완전히 삭제된다.레이아웃 계산 (Layout stage)
렌더 트리가 완성되면 렌더 트리의 각 노드를 사용하여 각 요소의 위치와 크기를 계산하는 레이아웃 과정(reflow라고도 함)을 수행할 수 있게 된다.
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width,initial-scale=1" /> <title>Critial Path: Hello world!</title> </head> <body> <div style="width: 50%"> <div style="width: 50%">Hello world!</div> </div> </body> </html>
위와 같은 HTML 본문에는 두 개의 중첩된
div
가 포함된다. 상위 div는 노드의 표시 크기를 표시 영역 너비 50%로 설정하고 하위 div는 상위 요소의 50%, 즉 표시 영역 너비의 25%로 설정한다.페이지에서 각 객체의 정확한 크기와 위치를 파악하기 위해 브라우저는 렌더 트리의 루트부터 순회를 시작한다. 각 요소의 크기와 위치는 콘텐츠 영역, 패딩, 보더, 마진으로 구성된 박스 모델을 기반으로 렌더 트리를 순회하며 각 요소의 크기(width, height)와 위치(top, left, right, bottom)를 계산한다.
일반적으로 위에서 아래로, 왼쪽에서 오른쪽으로 흐르며 부모 요소의 위치와 크기를 바탕으로 자식 요소들의 위치와 크기를 계산하여 박스 모델을 출력한다.
이제 DOM 트리와 스타일, 각 요소의 정확한 크기와 위치 정보(Box model)를 얻을 수 있었으니, 이 정보를 통해 최종 과정을 수행할 수 있다.
페인팅
HTML 문서의 구조(DOM), 각 요소의 스타일(CSSOM), 레이아웃(위치 및 크기 계산)을 알고 있지만 아직 화면을 그리진 않았다. 렌더 트리의 각 노드를 화면의 실제 픽셀로 변환하는 과정인 페인팅(래스터화라고도 함)을 수행한다.
각 요소의 스타일(색상, 테두리, 그림자 등)에 따라 화면에 표시되는 픽셀을 채워준다. 먼저, 렌더 트리의 각 노드에 대해 순차적으로 스타일을 적용하여 화면에 그린다. 색상, 폰트, 배경 이미지, 테두리, 그림자 등 다양한 스타일 속성을 적용한다. 이 결과로 화면의 각 요소가 픽셀 단위로 그려진 화면을 얻는다.
컴포지팅
모던 브라우저는 보이는 화면을 그대로 픽셀화 하지 않고, 컴포지팅이라는 방식을 사용한다. 마치 그래픽 편집기에서 사용되는 레이어(layer) 개념을 적용하여 페이지의 여러 요소를 레이어로 나누고 각각 픽셀화하여 이를 합성(Compositing)하는 과정을 거친다.
즉, 컴포지팅(Compositing, 합성)은 여러 레이어를 결합하여 최종 화면을 만드는 과정이다. 페인팅 과정의 결과물을 사용해서 레이어라이즈(Layerize)라는 과정을 거치는데, 이는 화면을 특정한 레이어들로 쪼개는 과정이다. 예를 들면 복잡한 레이아웃이나 애니메이션이 있는 요소들은 별도의 레이어로 쪼개서 처리한다.
레이어가 생성되는 조건은 여러가지가 있는데, CSS 속성이나 CSS 애니메이션, 하드웨어 가속을 사용해야 하는 경우(video, canvas, plugin 등), 요소의 position, 복잡한 렌더링이 필요한 속성 등이 있다.
브라우저 개발자 도구의 네트워크 탭에서 레이어 패널을 확인하면, 지금 보고 있는 화면을 그리는 데 사용된 레이어를 볼 수 있다.
이 일련의 과정을 모두 거치면 최종적으로 사용자의 브라우저가 화면을 보여줄 수 있다. 이 과정을 CRP라고 한다. 프론트엔드 개발자가 해야 할 일 중 매우 중요한 태스크가 바로 CRP 최적화다.
CRP를 어떻게 최적화할 수 있을까?
프론트엔드 개발자는 웹 페이지의 로딩 속도와 성능을 개선하여 사용자 경험을 향상시키기 위해 크리티컬 렌더링 패스를 최적화해야 한다. 여러 가지 기법과 도구를 사용할 수 있지만, 기본적인 골자는 앞서 설명한 CRP의 구분된 과정들에 당신의 웹 페이지의 성능을 고려하여 적절한 최적화를 수행해야 한다.
DOM 생성 최적화
- 불필요한 태그나 중복된 태그 삭제하고 시맨틱한 태그 사용
- HTML 파일의 크기를 줄임 (gzip이나 brotli와 같은 압축 기법으로 HTML 전송하기)
CSSOM 생성 최적화
- 여러 CSS 파일을 병합하고 불필요한 공백과 주석 제거
- 초기 화면을 보는 데 필요한 스타일만 포함하고, 나머지 스타일을 비동기적으로 로드하기
- CSS 파일 크기 줄이기 (압축 기법 사용)
Javascript 실행 최적화
- 비동기 및 지연 로드
- 초기 로딩에 필요한 스크립트만 로드하고 나머지는 필요한 시점에 로드 → 코드스플리팅
- 마찬가지로 파일 압축
렌더 트리 생성 및 레이아웃 계산 최적화
- 적절한 DOM 크기 유지하기 (너무 많은 노드를 사용하지 않기)
- 최소한의 CSS 복잡성 (복잡한 CSS 셀렉터와 중첩을 피하고 간단한 규칙 사용하기)
페인팅 및 컴포지팅 최적화
- 애니메이션과 전환을 사용하되, CSS
transform
과opacity
속성을 사용해서 페인팅 비용을 줄이기
- 불필요한 레이어 생성을 피하고, 필요한 경우
will-change
속성을 사용하여 브라우저가 미리 최적화할 수 있도록 하기
마무리
옛날에 비해 웹 애플리케이션이 매우 복잡하고 많은 기능을 수행하지만, 사용자들의 디바이스 성능이 상향 평준화된 것에 비해 웹 페이지 로딩 속도는 평균적으로 빨라지지 않았다. 웹 개발자라면 이에 책임감을 느껴야 한다더라.
렌더링 패스라는 거대한 주제를 깊게 탐구하기에는 아직 기본기(기반 지식과 경험)이 많이 부족하다. 아직 렌더링 파이프 라인까지 깊게 탐구하지도 못했다. 가볍게 브라우저가 어떤 과정을 통해 화면을그리는지 겉핥기만 한 수준인데, 언젠가는 꼭 이해할 수 있길 바라면서 포스팅을 마친다.