브라우저는 웹페이지를 어떻게 보여줄까요?
주소창에 글자를 몇 개 치고 엔터를 누르는 순간부터, 화면에 글씨와 사진이 자리를 잡기까지 걸리는 시간은 보통 1초가 채 되지 않습니다. 그 짧은 순간 사이에 화면은 새하얗게 비어 있다가, 어느 틈엔가 지금 여러분이 읽고 있는 이 문장처럼 정돈된 모습으로 바뀌어 있습니다. 대부분의 사람은 이 과정을 한 번도 의심하지 않습니다. 마치 전등 스위치를 누르면 불이 켜지듯, 당연한 일로 받아들이기 때문입니다.
그런데 이 새하얀 화면과 완성된 페이지 사이에는, 컴퓨터가 순서대로 처리해야 하는 수많은 판단의 층이 숨어 있습니다. 어떤 글자를 먼저 읽고, 어떤 요소를 화면 어디에 놓을지, 어떤 색을 입힐지, 그리고 이 모든 계산을 어떤 순서로 배열해야 화면이 깨지지 않을지를 브라우저는 매번 처음부터 다시 판단합니다. 사람이 눈치채지 못할 뿐, 실은 아주 정교한 조립 라인이 그 짧은 순간 안에서 쉬지 않고 돌아가고 있는 것입니다.
이 조립 라인이 어떻게 만들어졌는지, 그리고 지금 이 순간에도 여러분의 화면 뒤에서 무슨 일이 벌어지고 있는지를 하나씩 따라가 보겠습니다.
글자 뭉치가 도착하다
서버는 웹페이지를 그림으로 보내지 않습니다. 서버가 브라우저에 건네주는 것은 사실 아주 단순한 텍스트 파일입니다. <h1>, <p>, <img> 같은 태그로 이루어진 문자열 덩어리일 뿐, 그 안에는 색깔도 위치도 없습니다. 이 텍스트 파일을 받아 든 브라우저는 곧바로 이상한 일을 시작합니다. 문장을 통째로 이해하려 하지 않고, 작은 조각으로 잘게 썰어 버리는 것입니다.
이 과정을 파싱(parsing)이라고 부릅니다. 브라우저는 HTML 문서를 한 글자씩 훑으면서 <, h1, >처럼 의미 있는 최소 단위, 즉 토큰(token)으로 쪼갭니다. 그리고 이 토큰들을 다시 짜맞추어 문서 전체의 구조를 나무 모양으로 세웁니다. 이렇게 만들어진 나무를 DOM 트리(Document Object Model Tree)라고 부릅니다.
왜 하필 나무 모양일까요. HTML 문서 자체가 이미 포함 관계로 이루어져 있기 때문입니다. <body> 안에 <div>가 있고, 그 <div> 안에 다시 <p>와 <img>가 들어 있는 식으로, 태그는 서로를 품고 있습니다. 이 포함 관계를 그대로 살려 자료구조로 옮기면 뿌리에서 가지가 뻗어 나가는 나무 모양이 될 수밖에 없습니다. 브라우저는 이 나무 구조 덕분에 나중에 특정 요소 하나만 콕 집어 색을 바꾸거나 위치를 옮기는 일을 훨씬 빠르게 처리할 수 있습니다.
뼈대만으로는 아무것도 보이지 않는다
DOM 트리가 완성되었다고 해서 화면에 무언가가 나타나는 것은 아닙니다. DOM은 어떤 요소가 어떤 요소 안에 들어 있는지는 알려주지만, 그 요소가 무슨 색이고 얼마나 크며 화면 어디쯤 놓여야 하는지는 전혀 알지 못합니다. 뼈대는 완성되었지만, 아직 몸도 옷도 없는 상태인 셈입니다.
여기서 CSS가 등장합니다. 브라우저는 <link> 태그로 연결된 외부 CSS 파일과 문서 안에 직접 적힌 스타일 코드를 함께 읽어 들여, DOM과 비슷한 방식으로 CSSOM(CSS Object Model)이라는 또 하나의 나무를 만듭니다. 이 나무에는 어떤 요소가 몇 픽셀 크기이고, 무슨 색이며, 얼마나 간격을 두어야 하는지에 관한 규칙이 담겨 있습니다.
브라우저는 이제 두 개의 나무, DOM과 CSSOM을 하나로 합칩니다. 이렇게 만들어진 결과물이 렌더 트리(Render Tree)입니다. 브라우저는 DOM 트리의 맨 위 노드부터 아래로 내려가면서, 각 노드에 어울리는 CSSOM 규칙을 하나하나 찾아 붙입니다. 이 과정에서 흥미로운 규칙이 하나 적용됩니다. CSS로 display: none이 걸려 있거나 화면에 전혀 나타나지 않는 <head> 같은 요소는 렌더 트리에 아예 끼워 넣지 않습니다. 화면에 그려질 일이 없는 요소를 굳이 계산에 포함시킬 이유가 없기 때문입니다. 반대로 visibility: hidden으로 감춰진 요소는 눈에는 보이지 않아도 자리는 차지해야 하므로 렌더 트리 안에 그대로 남습니다. 보이지 않는 것과 존재하지 않는 것은, 적어도 브라우저의 셈법 안에서는 전혀 다른 문제입니다.
누가 어디에 설 것인가
렌더 트리까지는 완성되었지만, 여기에는 아직 좌표가 없습니다. “이 문단은 40픽셀 크기의 글자로 표시된다”는 규칙은 있어도, “그 문단이 화면의 정확히 몇 번째 줄, 몇 번째 픽셀에서 시작하는가”는 아직 정해지지 않았습니다. 브라우저는 이제 뷰포트, 즉 화면에 보이는 영역의 너비를 기준 삼아 각 요소가 실제로 차지할 위치와 크기를 계산합니다. 이 단계를 레이아웃(Layout), 또는 리플로우(Reflow)라고 부릅니다.
이 계산은 생각보다 훨씬 복잡합니다. 글자 하나의 크기를 정하려 해도 폰트, 줄 간격, 부모 요소의 너비, 화면 크기가 모두 얽혀 있어서, 단순히 위에서 아래로 한 번 훑는다고 끝나는 일이 아닙니다. 특히 부모 요소의 크기가 자식 요소에 따라 달라지는 경우에는 계산 순서 자체가 까다로워집니다. 그래서 창 크기를 조절하거나 폰트 크기를 바꾸면 브라우저가 이 레이아웃 계산을 처음부터 다시 해야 하고, 사용자는 이 순간 페이지가 살짝 버벅이는 느낌을 받게 됩니다.
레이아웃까지 끝나면 마지막 단계가 남습니다. 렌더 트리의 각 노드를 실제 화면의 픽셀로 바꾸는 페인트(Paint) 과정입니다. 색을 칠하고, 그림자를 그리고, 테두리를 두르는 이 작업은 여러 개의 레이어로 나뉘어 처리된 뒤, 합성(Compositing)이라는 과정을 거쳐 하나의 화면으로 겹쳐집니다. 최신 브라우저는 이 합성 작업의 상당 부분을 그래픽 카드에 맡깁니다. 스크롤을 내릴 때 화면이 부드럽게 움직이는 것은, 매번 전체 페이지를 새로 그리는 대신 이미 그려 둔 레이어를 그래픽 카드가 살짝 밀어서 보여주기 때문입니다.
자바스크립트라는 불청객
여기까지는 HTML과 CSS만 있다면 비교적 순탄하게 흘러갑니다. 문제는 자바스크립트가 끼어들 때 생깁니다. 자바스크립트는 DOM과 CSSOM을 마음대로 바꿀 수 있는 유일한 언어입니다. 그래서 브라우저는 HTML을 파싱하다가 <script> 태그를 만나면, 그 즉시 파싱을 멈추고 스크립트 파일을 내려받아 실행부터 마쳐야 합니다. 스크립트가 그 사이에 문서 구조를 바꿔버릴 수도 있기 때문입니다.
이 규칙 때문에 웹페이지 개발자들은 오랫동안 골머리를 앓았습니다. <script> 태그를 문서 위쪽에 두면 페이지 전체가 그 스크립트 하나 때문에 멈춰 서 버립니다. 사용자 눈에는 몇 초 동안 화면이 새하얗게 멈춘 것처럼 보이게 됩니다. 그래서 오늘날 많은 사이트는 스크립트를 문서 맨 아래로 옮기거나, defer나 async 같은 속성을 붙여 파싱을 방해하지 않도록 우회합니다. 스타일시트도 비슷한 골칫거리입니다. CSS 자체는 DOM 구조를 바꾸지 않지만, 아직 스타일이 도착하지 않은 상태에서 자바스크립트가 스타일 정보를 물어보면 엉뚱한 값을 받게 되므로, 브라우저는 이런 충돌을 피하려고 스타일시트가 완전히 로드될 때까지 스크립트 실행을 미루기도 합니다.
가끔 웹페이지를 열었을 때, 아주 잠깐 글자만 덩그러니 있다가 뒤늦게 디자인이 입혀지는 장면을 본 적이 있을 것입니다. 이 현상에는 이름도 있습니다. 스타일 없는 콘텐츠가 잠깐 노출된다고 해서 FOUC(Flash of Unstyled Content)라고 부릅니다. CSSOM보다 DOM이 먼저 완성되어 화면에 그려질 때 벌어지는 일로, 브라우저가 무엇을 먼저 처리하고 무엇을 나중에 처리하는지를 그대로 드러내는 흔적이라 할 수 있습니다.
같은 코드, 다른 그림
지금까지 설명한 단계, 즉 HTML을 읽어 DOM을 만들고 CSS로 CSSOM을 만들어 렌더 트리로 합친 뒤 레이아웃과 페인트를 거치는 흐름을 브라우저 업계에서는 CRP(Critical Rendering Path), 우리말로 옮기면 ‘중요 렌더링 경로’라고 부릅니다. 그런데 이 경로를 실제로 실행하는 프로그램, 이른바 렌더링 엔진은 브라우저마다 다릅니다.
이 사실이 낯설게 느껴질 수도 있습니다. 크롬으로 보든 사파리로 보든 같은 웹사이트는 같은 모습이어야 할 것 같은데, 실제로는 그렇지 않은 경우가 종종 있습니다. 똑같은 HTML과 CSS 코드를 주더라도, 그 코드를 해석해서 렌더 트리로 바꾸고 픽셀로 그려내는 엔진 자체가 다르면 미묘하게 다른 결과가 나올 수 있습니다. 글자 사이 간격이 살짝 다르거나, 그림자의 번짐 정도가 다르거나, 최신 CSS 기능 하나가 어느 브라우저에서는 작동하고 다른 브라우저에서는 무시되는 식입니다.
| 렌더링 엔진 | 주요 사용 브라우저 | 계보 |
|---|---|---|
| Blink | 크롬, 마이크로소프트 엣지, 브레이브, 오페라 | 2013년 애플 웹킷에서 구글이 갈라져 나와 개발 |
| WebKit | 사파리 | 2001년 애플이 KDE의 KHTML을 기반으로 개발 |
| Gecko | 파이어폭스 | 1998년 넷스케이프가 오픈소스로 공개한 코드에서 출발 |
이 세 갈래는 우연히 나뉜 것이 아니라, 1990년대부터 이어진 치열한 경쟁의 흔적입니다.
브라우저 전쟁이 남긴 것
최초의 웹 브라우저는 의외로 한 사람의 손에서 나왔습니다. 유럽입자물리연구소(CERN)의 연구원이었던 팀 버너스리는 1990년, 서로 다른 컴퓨터와 운영체제를 쓰는 전 세계 연구자들이 자료를 주고받지 못해 겪는 불편함을 해결하기 위해 웹이라는 개념 자체를 고안했습니다. 그가 만든 최초의 웹 브라우저이자 편집기는 원래 ‘월드와이드웹(WorldWideWeb)’이라는 이름이었다가 훗날 ‘넥서스(Nexus)’로 이름이 바뀌었으며, 1991년에는 웹의 기반이 되는 마크업 언어를 발표하고 1993년에는 브라우저의 소스 코드까지 공개했습니다.
이 공개가 예상치 못한 결과를 낳았습니다. 미국 일리노이 대학의 학생이었던 마크 앤드리슨이 이끄는 팀이 1993년 4월, 이미지와 텍스트를 한 화면에 함께 보여주는 그래픽 브라우저인 모자이크(Mosaic)를 내놓으며 일반인에게도 웹을 쉽게 열어 보였고, 이는 1990년대 인터넷 붐을 촉발하는 계기가 되었습니다.모자이크를 만든 주역들은 이듬해 회사를 나와 넷스케이프를 세우고 넷스케이프 내비게이터를 출시했습니다.
여기서부터 이른바 브라우저 전쟁이 시작됩니다. 마이크로소프트는 윈도우 운영체제에 인터넷 익스플로러를 기본으로 끼워 넣어 배포했고, 2003년 무렵에는 점유율이 95%에 이를 정도로 시장을 장악했습니다. 문제는 이 지배력이 웹 표준을 무시하는 결과로 이어졌다는 점입니다. 각 회사가 자기 브라우저에서만 작동하는 독자적인 기능을 밀어붙이면서, 개발자들은 같은 웹사이트를 브라우저마다 다르게 만들어야 하는 처지에 놓였습니다. 오늘날 DOM과 CSSOM이라는 표준화된 개념이 자리 잡은 것은, 이 혼란을 정리하려는 오랜 노력의 결과물이라고 볼 수 있습니다.
애플의 사파리가 쓰는 웹킷은 원래 KDE 진영의 오픈소스 엔진 KHTML을 애플이 가져다 다듬은 결과물이었습니다. 그리고 2013년, 구글은 자사 크롬에 쓰던 웹킷 기반 코드를 독립시켜 블링크라는 이름을 붙였습니다. 겉보기에는 사소한 사건 같지만, 이 갈라섬 이후 웹 생태계의 무게 중심은 눈에 띄게 블링크 쪽으로 옮겨갔습니다. 2026년 기준으로 크롬 기반의 블링크 엔진은 전 세계 웹 세션의 4분의 3 이상을 처리하고 있으며, 사파리가 그 뒤를 잇고 파이어폭스는 한 자릿수 점유율에 머물러 있습니다. 측정 기관에 따라 구체적인 수치는 조금씩 엇갈리지만, 크롬 계열이 압도적 우위를 지키고 있다는 큰 그림만큼은 대부분의 조사에서 일치합니다.
다시, 처음의 그 순간으로
이제 처음 장면으로 돌아가 보겠습니다. 여러분이 주소창에 무언가를 치고 엔터를 누른 순간, 서버에서 도착한 것은 그저 태그로 이루어진 텍스트 뭉치였습니다. 브라우저는 이 뭉치를 토큰으로 쪼개 DOM 트리를 세우고, CSS를 읽어 CSSOM을 만들고, 둘을 합쳐 렌더 트리를 완성했습니다. 화면 크기에 맞춰 각 요소의 위치를 계산하는 레이아웃을 거치고, 마지막으로 픽셀 하나하나에 색을 입히는 페인트와 합성을 마쳤습니다. 이 모든 일이, 여러분이 화면을 쳐다보는 그 짧은 순간 안에서 끝났습니다.
같은 코드를 받아 들고도 크롬과 사파리와 파이어폭스가 조금씩 다른 그림을 그려내는 이유도 이제는 낯설지 않을 것입니다. 세 갈래로 나뉜 렌더링 엔진의 계보 뒤에는, 한 연구자의 소박한 발명과 그 뒤를 이은 회사들의 치열한 다툼이 겹겹이 쌓여 있습니다.
다음에 어느 사이트가 유독 느리게 열리거나, 잠깐 글자만 덩그러니 보이다가 디자인이 뒤늦게 입혀지는 장면을 마주하게 된다면, 그 몇 초 사이에 브라우저가 무엇을 먼저 하고 무엇을 나중으로 미뤘는지 한번 떠올려 보시기 바랍니다.
자주 묻는 질문
웹페이지마다 로딩 속도가 다른 이유는 무엇인가요?
이미지 용량이나 서버 응답 속도 같은 네트워크 요인도 있지만, 렌더링 관점에서는 CSS와 자바스크립트의 배치 순서가 큰 영향을 미칩니다. 문서 위쪽에 무거운 스크립트가 있으면 브라우저는 파싱을 멈추고 그 스크립트부터 처리해야 하므로, 같은 내용이라도 코드 구조에 따라 체감 속도가 크게 달라질 수 있습니다.
크롬과 사파리는 왜 같은 사이트를 다르게 보여줄 때가 있나요?
두 브라우저가 서로 다른 렌더링 엔진, 즉 크롬은 블링크, 사파리는 웹킷을 사용하기 때문입니다. 최신 CSS 기능 하나가 한쪽 엔진에는 구현되어 있고 다른 쪽에는 아직 없는 경우도 있어, 똑같은 코드를 주어도 미묘하게 다른 결과가 나올 수 있습니다.
렌더링 엔진과 자바스크립트 엔진은 같은 것인가요?
다릅니다. 렌더링 엔진은 HTML과 CSS를 화면의 픽셀로 바꾸는 역할을 맡고, 자바스크립트 엔진은 스크립트 코드를 실행하는 별도의 프로그램입니다. 크롬의 경우 렌더링은 블링크가, 자바스크립트 실행은 V8이라는 별도 엔진이 담당합니다.
새로고침을 하면 브라우저는 모든 과정을 처음부터 다시 하나요?
기본적으로는 그렇습니다. HTML 파싱부터 DOM 구축, 레이아웃, 페인트까지 전체 과정을 다시 거칩니다. 다만 이미지나 스타일시트 같은 일부 자원은 브라우저에 저장된 캐시를 재사용하는 경우가 많아, 완전히 처음 방문할 때보다는 대체로 더 빠르게 끝납니다.
FOUC는 왜 생기나요?
DOM 트리가 CSSOM보다 먼저 완성되어 브라우저가 스타일 없는 상태로 화면을 먼저 그려버릴 때 나타나는 현상입니다. 스타일시트 다운로드가 늦어지거나 스타일시트 위치가 문서 아래쪽에 있을 때 자주 발생하며, 최신 브라우저들은 대부분 스타일시트가 준비될 때까지 화면 그리기를 미루는 방식으로 이 문제를 줄이고 있습니다.