브라우저가 페이지를 처리하는 과정을 이해하는 이유
브라우저가 페이지를 로딩하고 해석하며 렌더링하는 방식을 알면, 외부 스타일시트나 자바스크립트 파일을 적절한 위치에 배치함으로써 최적의 로딩 성능을 달성할 수 있습니다. 또한, DOM 구조 설계 및 선택자 작성 시 효율적인 방법을 선택해 해석 속도를 향상시킬 수 있으며, 요소 스타일 설정이나 자바스크립트 코드 작성 시 재렌더링과 리레이아웃 재계산을 줄일 수 있습니다.
이 세 가지 단계는 서로 독립적이기보다는 상호작용하며 동시에 진행됩니다. 즉, 리소스를 다운로드하면서 해석을 시작하고, 해석 결과를 바탕으로 렌더링을 수행하는 병렬적인 흐름이 형성됩니다.
브라우저의 처리 흐름
- 사용자가 웹사이트에 접속하면, DNS 서버가 도메인 이름을 기반으로 해당 주소에 대응하는 IP 주소를 검색합니다.
- 이후 요청은 해당 IP 주소의 서버로 전송되며, 서버는 요청을 처리하기 위해 데이터베이스에 접근합니다.
- 데이터베이스에서 필요한 정보를 받아와 서버가 응답 본문(HTML)을 생성하고, 이를 클라이언트인 브라우저에게 반환합니다.
- 브라우저는 받은 HTTP 응답을 분석하고, 문서 본문 및 참조된 리소스(이미지, CSS, JS 등)를 다운로드합니다.
이 과정은 HTTP 프로토콜의 작동 원리를 이해하는 것이 필수적입니다. 오류 발생 시에는 일반적으로 다음과 같은 상태 코드가 나타납니다:
- 404 Not Found: 요청한 리소스가 존재하지 않음
- 500 Internal Server Error: 서버 내부 오류로 요청 처리 불가
로딩 단계: 리소스 다운로드
브라우저는 HTML 문서를 먼저 수신한 후, 문서 내부에 포함된 외부 리소스(예: 스타일시트, 자바스크립트, 이미지)를 별도 요청하여 다운로드합니다. 이 과정은 비동기적으로 이루어져 일반적으로 문서 파싱을 방해하지 않습니다.
다만, <script> 태그는 예외입니다. 자바스크립트 파일을 만나면, 브라우저는 파싱을 일시 중단하고, 스크립트 파일의 다운로드와 실행을 완료해야 다음 파싱을 진행할 수 있습니다. 이는 자바스크립트가 document.write() 등을 통해 DOM을 변경할 수 있기 때문입니다. 따라서, 문서의 후반부에 스크립트를 배치하는 것이 일반적인 최적화입니다.
또한, <link rel="stylesheet">는 자바스크립트 실행을 방해하지 않지만, 자바스크립트가 getComputedStyle()나 offsetWidth 같은 스타일 관련 메서드를 호출할 경우, 스타일 파일이 완전히 로드되고 해석된 이후에야 실행될 수 있습니다. 이 때문에 스타일 파일은 자바스크립트 실행을 지연시키는 요인이 됩니다.
이러한 문제를 해결하기 위해, 스타일 파일이 필요 없는 스크립트는 <head> 섹션에서 스타일 시트보다 앞에 위치시킬 수 있습니다.
해석 단계: 구조화
브라우저는 수신한 HTML을 해석하여 DOM 트리로 변환합니다. 이 트리는 문서의 모든 요소와 속성을 계층적으로 표현한 객체 구조이며, 루트 노드는 document입니다.
스타일 시트는 CSS 규칙 객체로 해석되어, 선택자와 선언(속성-값 쌍)을 포함한 구조로 저장됩니다. 이 과정에서 선택자의 우선순위(레벨)와 상속 관계가 결정됩니다.
자바스크립트 파일은 로딩과 동시에 해석되며, 이 과정에서 코드가 실행되기 전까지는 다음 리소스의 처리가 지연될 수 있습니다.
렌더링 단계: 시각적 출력 준비
렌더링은 실제 화면에 표시될 수 있도록 렌더 트리를 생성하는 과정입니다. 이 트리는 레이아웃과 렌더링을 위한 정보를 포함하며, 다음 요소들은 렌더 트리에 포함되지 않습니다:
- <head> 내부 요소
- display: none로 숨겨진 요소
- 절대/플로팅 정렬된 요소(텍스트 흐름 밖)
렌더 트리의 각 노드는 실제 위치를 반영하되, 원래의 위치는 자리표시(백업)로 유지됩니다.
스타일 계산의 어려움
각 요소에 대해 가장 적합한 스타일을 계산하는 것은 매우 복잡합니다. 특히 다음 문제들이 발생합니다:
- 스타일 규칙 집합이 크고 메모리 소비가 큼
- 깊은 선택자 계층(예:
#nav ul li a)은 불필요한 탐색을 유발 - 스타일의 급속 연쇄(cascading) 규칙이 복잡함 (예:
body {font-size:12px}→ 자식 요소에서 재정의됨)
공유 기법을 활용해 동일한 상태를 가진 요소들 간에 스타일 데이터를 공유함으로써 성능을 개선할 수 있습니다.
WebKit 렌더링 구조
WebKit는 각 요소에 대해 attach() 메서드를 호출하여 스타일 정보를 결합하고, 렌더 트리를 구성합니다. 이 과정은 동기적입니다. 만약 스타일 시트가 아직 로드되지 않았다면, 임시 표시를 넣고 나중에 다시 계산합니다.
이로 인해, 전체 렌더링은 최상위 스타일 시트가 모두 로드된 이후에 시작됩니다.
Gecko 렌더링 구조
Gecko는 스타일 규칙 트리를 생성한 후, 이를 기반으로 스타일 컨텍스트 트리를 만들어 렌더 트리를 형성합니다.
예시:
<html>
<body>
<div class="err" id="div1">
<p>this is a <span class="big">big error</span></p>
</div>
<div class="err" id="div2">another error</div>
</body>
</html>
/* 스타일 규칙 */
1. div {margin:5px; color:black}
2. .err {color:red}
3. .big {margin-top:3px}
4. div span {margin-bottom:4px}
5. #div1 {color:blue}
6. #div2 {color:green}
Gecko는 각 요소에 대해 여러 규칙을 매칭하고, 공통된 부분을 공유함으로써 효율성을 높입니다. 예를 들어, 두 번째 <div>는 규칙 1, 2, 6을 적용하며, 규칙 1과 2는 첫 번째 <div>에서도 사용되었으므로 새 노드를 추가하지 않고 기존 트리에 연결합니다.
최적화 팁
- DOM 계층 구조는 가능한 한 얕게 유지하세요.
- 인라인 스타일과 자바스크립트는 최소화하세요.
- 현대적인 유효한 CSS 속성만 사용하세요.
id선택자는 클래스나 태그를 함께 사용하지 마세요 (중복).- 클래스 선택자는 특정 태그를 지정하지 마세요 (클래스는 다수의 요소에 공유 가능).
- 후손 선택자(
parent child)보다 하위 선택자(parent > child)를 사용하세요. 효율성이 더 높습니다. - 전체 선택자(
*)는 피하세요. 예:.mod .hd * {font-size:14px}는 모든 요소를 순회하며 부모 요소를 확인해야 하므로 성능 저하를 초래합니다.
고급 기술
prefetch: 브라우저가 미래에 방문할 가능성이 있는 페이지의 리소스를 미리 다운로드합니다.<link rel="prefetch" href="https://example.com/page.html">defer속성: 스크립트가 문서 파싱 후, 완전히 로드된 후에 실행되도록 합니다. 이미지 로딩과 병행 가능.<script defer src="app.js"></script>