성능 최적화와 브라우저 렌더링 원리는 밀접하게 연관되어 있다. DOM, CSSOM, 그리고 렌더 트리 생성 및 업데이트 과정에서 발생하는 비용을 줄이는 것이 성능 최적화의 핵심인데, 이 과정에서 브라우저가 화면을 그릴 때 발생하는 다양한 비용을 최소화하는 것이 중요하다. 예를 들어, DOM 조작이나 CSS 변경, 레이아웃 계산, 애니메이션 등의 작업은 렌더링 과정을 트리거하는 요소들이다. 이러한 작업들을 최소화하면 브라우저는 더 적은 리소스를 사용해 화면을 그릴 수 있다.
따라서 브라우저 렌더링 원리를 이해하는 것이 웹 성능 문제를 해결하는 데 큰 도움이 된다. 렌더링 과정에서 어느 단계에서 성능 문제가 발생하는지 빠르게 파악할 수 있고, 문제의 원인을 정확히 이해한 뒤 해결 방법을 신속하게 찾을 수 있기 때문이다. 웹 성능 최적화를 위한 첫걸음은 바로 이 렌더링 원리를 이해하는 것이라고 할 수 있다.
브라우저는 운영 체제(OS) 위에서 실행되는 애플리케이션으로, 사용자가 웹사이트를 탐색하고 정보를 검색하며 소통할 수 있도록 돕는다. 브라우저가 이러한 역할을 할 수 있는 이유는 다양한 구성 요소들이 협력하여 웹 페이지를 로드하고 렌더링하며 사용자와 상호작용하는 방식으로 작동하기 때문이다. 브라우저는 다음과 같은 주요 요소들로 구성된다.

1. 사용자 인터페이스(User Interface)
사용자가 브라우저와 상호작용하는 주요 인터페이스이다. 주소창, 뒤로 가기/앞으로 가기 버튼, 탭, 북마크, 메뉴 등 다양한 기능을 제공한다.
2. 브라우저 엔진 (Browser Engine)
사용자 인터페이스와 렌더링 엔진 사이의 인터페이스 역할을 한다. 주어진 URL 을 읽어, 앞/뒤 페이지로 가거나 새로고침을 하는 등 기초적인 액션을 담당한다.
3. 렌더링 엔진 (Rendering Engine)
HTML, CSS, JavaScript 파일을 해석하여 화면에 웹 페이지를 렌더링한다. 웹 페이지를 시각적으로 구성하는 핵심 엔진이다.
렌더링 엔진은 사용하는 브라우저마다 다르다.
Google : Blink
IE : Trident
FireFox : Gecko
Crome for IOS : WebKit
4. 네트워크 레이어(Network)
서버와의 통신을 담당하며, HTTP 요청 및 응답을 처리한다. 브라우저가 웹 페이지나 리소스를 요청하고, 이를 서버에서 받아오는 과정이 포함된다.
5. 자바스크립트 인터프리터(JavaScript Interpreter)
웹 페이지에서 동적으로 실행되는 JavaScript 코드를 해석하고 실행한다.웹 페이지 내의 JavaScript 코드를 실행하여 DOM을 조작하고, 사용자와의 상호작용을 처리한다.
6. ui 백엔드
os에서 제공하는 UI메서드를 사용해 버튼, 콤보박스 등 기본적인 위젯을 그려준다.
7. 스토리지 레이어
자료를 저장하는 레이어다. 쿠키나 로컬/세션 스토리지 등 모든 종류의 자원을 하드디스크에 저장한다.
** 사용자가 URI를 입력하면 브라우저 렌더링의 구성요소들이 어떻게 동작하는지까지 정리하고 싶지만 오늘의 주제인 렌더링 원리에서는 좀 벗어나는 거 같아 그건 따로 적지 않겠다!
이제 렌더링 엔진이 리소스를 처리하고 UI를 화면에 표시하는 부분에 대해 좀 더 자세히 살펴보자.

1) HTML 파싱 → DOM Tree 생성
2) CSS 파싱 → CSSOM Tree 생성
3) DOM Tree + CSSOM Tree → Render Tree 생성
4) Layout: Render Tree 기반으로 요소의 위치와 크기 계산
5) Paint: Render Tree와 Layout 정보로 화면에 그릴 픽셀 준비
6) Composite: 픽셀을 화면에 최종 렌더링
DOM (Document Object Model) Tree는 HTML 문서를 브라우저가 파싱하여 계층적 트리 구조로 표현한 객체 모델이다. 과정은 아래와 같이 이루어진다.
1. HTML 문서로드
2. 토큰화 (Tokenization)
3. 트리 생성 (Tree Construction)
예시 코드
<html>
<head>
<title>Document</title>
</head>
<body>
<div>
<p>Hello, World!</p>
</div>
</body>
</html>
예시 DOM 트리 구조
html
├── head
│ └── title
└── body
└── div
└── p
└── "Hello, World!"
HTML 문서를 모두 파싱한 후 최종적으로 DOM Tree가 완성된다.
CSSOM (CSS Object Model) Tree는 CSS 문서를 파싱하여 브라우저가 스타일 정보를 표현하는 계층적 트리 구조이다. 과정은 아래와 같이 이루어진다.
1. CSS 리소스 로드
<style> 태그나 <link> 태그를 만나면 CSS 리소스를 가져온다.2. CSS 파싱 시작
3. 토큰 → 스타일 규칙 변환
선택자: 스타일이 적용될 HTML 요소
선언 블록: 선택자에 적용될 스타일 속성과 값
4. CSSOM Tree 생성
CSSOM Tree
- body
├── margin: 0
└── font-size: 16px
- h1
├── color: blue
└── text-align: center
모든 CSS를 파싱하고 상속, 우선순위, 미디어 쿼리 등을 처리한 뒤 최종적으로 CSSOM Tree를 완성한다.
Render Tree는 브라우저가 화면에 요소를 배치하고 그릴 때 필요한 구조로, DOM Tree와 CSSOM Tree를 결합하여 생성된다.Render Tree는 화면에 실제로 표시되어야 하는 노드만 포함하며, 각 노드에 스타일과 레이아웃 정보를 담는다.
DOM Tree와 CSSOM Tree 결합
시각적 노드만 포함
display: none;: 완전히 숨겨지는 요소.(DOM Tree와 CSSOM Tree에는 포함되지만, Render Tree에는 포함되지 않음)
메타 태그와 같은 시각적으로 표시되지 않는 요소.
Render Tree 생성
Layout은 Render Tree를 기반으로 각 요소의 위치와 크기를 계산하는 단계이다. 이 과정은 박스 모델(Box Model)텍스트을 적용하여 요소들이 화면에 어떻게 배치될지 결정한다.
상대적 크기와 위치 계산
플로우와 컨텍스트 설정
뷰포트와 스크롤 영역 계산
❗️ Layout은 문서 전체 또는 일부를 다시 계산해야 하므로 성능에 큰 영향을 미친다.
Paint는 Layout 단계에서 계산된 요소의 위치와 스타일을 기반으로 각 요소를 픽셀 단위로 표현하는 단계이다.
브라우저는 이 단계에서 요소의 시각적 속성(색상, 그림자, 배경, 텍스트 등)을 픽셀 단위로 계산하고 그린다.
각 요소에 대한 스타일 처리
레이어 생성
Composite 단계는 GPU가 여러 레이어에서 준비된 픽셀 데이터를 결합하여 최종적으로 화면에 렌더링하는 단계이다. 레이어 수가 많을수록 합성이 오래 걸리기 때문에 레이어 수가 많으면 성능에 좋지 않다.
DOM Tree를 생성하는 동안 JavaScript 파일을 동기적으로 불러오면 HTML 파싱이 중단된다.
❓ 브라우저는 HTML 문서를 위에서 아래로 순차적으로 파싱하며, 이 과정에서<script>태그를 만나면 잠시 파싱을 중단한다. 이는 JavaScript 코드가 HTML 문서의 상태에 영향을 줄 수 있기 때문이다. 브라우저는 해당 스크립트를 로드하고 실행한 후에야 다시 HTML 파싱을 재개하므로, 전체 파싱 시간이 늘어날 수 있다.
<script> 태그는 가능한 한 HTML의 body 끝에 위치하도록 하자. defer 속성 사용 :<script src="script.js" defer></script>async 속성 사용:<script src="script.js" async></script> CSS 파일의 크기와 복잡성이 CSSOM 트리 생성 성능에 영향을 미친다.
❓ 브라우저는 HTML을 파싱하면서 스타일 시트를 로드하여 CSSOM 트리를 생성한다. 이 과정에서 CSS 파일이 너무 크거나 복잡하면 브라우저가 이를 해석하는 데 시간이 더 걸리게 된다. 특히, 여러 개의 CSS 파일을 불러오거나 복잡한 선택자가 많을 경우, CSSOM 트리 생성 시간이 길어져 렌더링 성능에 영향을 줄 수 있다.
1. CSS 파일 크기는 가능한 한 최적화하자.
여러 개의 CSS 파일을 병합하여 하나의 파일로 만드는 것이 HTTP 요청 수를 줄이고 로딩 시간을 단축시킬 수 있다. (** SSR일 경우)
2. 사용하지 않는 CSS는 제거하자.
사용하지 않는 CSS를 제거하면, 불필요한 스타일 규칙이 CSSOM 트리에 포함되지 않아 CSSOM 트리 크기를 줄인다. 또한, 파일 크기가 줄어들어 페이지 로딩 시간을 단축할 수 있다.
3. CSS 복잡성 최소화하기.
복잡한 선택자나 스타일 규칙이 많으면 브라우저가 이를 해석하는 데 시간이 더 걸린다. !important나 중첩된 선택자들을 과도하게 사용하는 경우 성능에 악영향을 미칠 수 있다.
CSS 선택자가 깊이가 깊거나 불피요한 태그가 중첩되어 있으면 Render Tree 생성이 느려질 수 있다.
❓ Render Tree를 생성하려면 브라우저는 HTML의 각 요소(DOM 노드)에 적합한 스타일을 적용하기 위해 CSSOM과 DOM Tree를 결합해야 한다. 이 과정에서 브라우저는 CSS 선택자를 DOM 요소에 일치시키는 작업을 수행힌다. 예를 들어, header > div > ul > li > span > p와 같은 CSS 선택자로 스타일이 정의된 경우, 브라우저는 p 태그를 만날 때마다 모든 계층의 부모 요소를 확인해야 하므로 연산 비용이 증가한다. 또한, 태그가 불필요하게 중첩되어 있다면 스타일을 적용하는 과정에서 부모 요소를 하나하나 확인해야 하므로 성능이 더욱 저하될 수 있다.
1. CSS 선택자를 단순화하고 클래스를 활용하자.
복잡한 CSS 선택자 header > div > ul > li > span > p 대신 클래스나 ID를 사용하자.
/* 비효율적 */
header > div > ul > li > span > p { color: red; }
/* 효율적 */
.highlight { color: red; }
2. 복잡한 DOM 트리를 지양하자.
DOM 트리가 깊고, 자식 요소가 많을수록 DOM 트리는 커지고 그에 따른 계산이 추가되므로, 불필요한 태그 중첩을 최소화 하자.
/* 비효율적 */
<div>
<div>
<div>
<p>텍스트</p>
</div>
</div>
</div>
/*효율적*/
<div>
<p>텍스트</p>
</div>
불필요한 리플로우(리렌더링)를 최소화 해야 한다.
❓ 리플로우(Reflow)는 DOM의 구조나 스타일에 변화가 생기면 브라우저가 새로 레이아웃을 계산하는 과정을 말한다. 브라우저는 요소의 크기, 위치, 정렬 등을 계산하여 최종 화면에 표시될 요소들을 결정하는데, 이 과정에서 많은 계산과 메모리 작업이 발생한다. 특히, 레이아웃이 여러 번 재계산되면 성능에 큰 부하를 일으킬 수 있다.
1. DOM 변경을 한번에 처리하자.
여러 개의 DOM 변경이 필요한 경우, 한 번에 처리하여 리플로우를 최소화해야 한다. DOM을 여러 번 수정하는 것보다 한 번에 수정하면 브라우저는 레이아웃을 한 번만 계산하게 된다.
2. 상위 요소에서 레이아웃 변경을 최소화하자.
부모 요소나 상위 요소의 레이아웃이 변경되면 자식 요소의 레이아웃도 함께 변경되어 성능에 영향을 미친다. 가능한 한 자식 요소에만 스타일을 변경하거나, 부모 요소의 크기나 위치를 고정하여 상위 요소의 레이아웃 변경을 피하는 것이 좋다.
3.스타일을 동적으로 변경할 때 GPU에서 처리하도록 하자.
스타일을 동적으로 변경할 때, 특히 width, height, padding, margin, top, left 등의 레이아웃에 영향을 주는 스타일 속성은 리플로우를 유발한다.
그러나 transform과 opacity 속성은 레이아웃에 영향을 미치지 않고, GPU에서 하드웨어 가속을 받아 처리되기 때문에 애니메이션이나 스타일 동적으로 변경할 때는 transform과 opacity를 사용하는 것이 좋다.
4. 요소를 숨길때 visibility: hidden 사용하자.
display: none은 요소 자체를 숨기기 때문에 DOM 변경이 일어나기 때문에, 요소를 숨길 때 display: none을 사용하는 것보다 visibility: hidden을 사용하는 것이 성능에 유리하다.
5. Reflow를 발생시키는 CSS 속성을 피하자.
box-sizing / border-color / text-align / border / border-width /
font-family / float / font-size / font-weight / line-height / vertical-align /
white-space / word-wrap / text-overflow / text-shadow ...
불필요한 리페인트를 최소화 해야 한다.
❓ 리페인트(Paint)는 요소의 스타일이나 색상이 변경될 때 발생하며, 리플로우보다는 비용이 적게 들지만 여전히 성능에 영향을 미칠 수 있다. 브라우저는 스타일이나 색상 변경에 따라 화면에 다시 그리기를 수행하며, 이 과정이 반복되면 화면 깜빡임이나 렌더링 지연이 발생할 수 있다. 불필요한 리페인트를 최소화하면 성능을 최적화하고, 더욱 부드러운 사용자 경험을 제공할 수 있다.
1. 스타일을 변경을 한 번에 처리하자.
스타일 변경은 리페인트를 유발하므로, 자주 스타일을 변경하는 것은 성능에 영향을 미친다. 여러 번 스타일을 변경하기보다는, 한 번에 여러 스타일을 변경하는 것이 좋다.
2. Repaint를 발생시키는 CSS 속성을 피하자.
background-image / background-position / background-repeat / background-size /
text-decoration / outline / outline-style / outline-color / outline-width /
border-radius / box-shadow ...