CSS로 인터랙션을 구현할 때 width, position 속성값 대신 transform 속성값을 사용하라는 이야기 들어보셨나요? 저는 웹 퍼블리싱에 입문한지 얼마 안 되어 해당 이야기를 들었지만, 그 당시에는 이유를 자세히 알지 못했습니다. 그냥 transform이 성능상 더 좋다고만 알고 넘어갔는데요. 그러다 브라우저 렌더링 과정, 특히 주요 렌더링 경로 (CRP, Critical Rendering Path)를 알게 되면서 CSS 속성별로 성능 차이가 나는 이유를 알게 되었습니다. 오늘은 주요 렌더링 경로에 대해 알아보며 브라우저 렌더링 성능을 향상하는 방법에 대해서도 알아보겠습니다.
주요 렌더링 경로란?
주요 렌더링 경로(CRP, Critical Rendering Path)는 브라우저가 HTML, CSS, 자바스크립트를 픽셀 데이터로 변경하여 화면을 그려내는 일련의 단계, 즉 브라우저가 서버로부터 전달받은 데이터를 화면에 그려내는 과정을 의미합니다.
참고로 MDN은 중요 렌더링 경로로 번역되어 있는데, 다른 책들을 보면 주요 렌더링 경로라고 하더라고요. 주요 렌더링 경로가 더 적절한 표현이라고 생각되어 저도 주요 렌더링 경로라고 하겠습니다.
주요 렌더링 경로 알아보기
그럼 주요 렌더링 경로 단계를 자세히 살펴보겠습니다.

주요 렌더링 경로 단계
브라우저가 화면을 렌더링 할 때 요청/응답, 로딩, 스크립팅, 렌더링, 레이아웃, 페인팅 단계를 거칩니다.
1. 요청 / 응답 (Request / Response)
브라우저가 요청을 보내면 서버가 HTML 데이터를 응답합니다.
2. 로딩 (Loading)

HTML을 파싱하여 생성된 DOM Tree
이미지 출처 : https://developer.mozilla.org/ko/docs/Web/Performance/How_browsers_work
- 브라우저가 HTML을 파싱하여 DOM Tree를 생성합니다. 이때 파싱은 HTML과 같은 데이터를 브라우저가 이해할 수 있는 객체(DOM)로 변환하는 작업을 의미합니다.
- HTML 파싱 중에 발견된 CSS, 자바스크립트, 이미지 등 외부 자원을 요청합니다.
- CSS 파일을 파싱하면서 DOM과 CSS를 결합한 CSSOM(CSS Object Model)을 생성합니다.
- 이 때 CSS, 자바스크립트를 파싱하기 위해 HTML 파일 파싱이 중단됩니다. CSS, 자바스크립트 파싱이 완료된 후 HTML 파싱이 재개됩니다.
3. 스크립팅 (Scripting)
자바스크립트를 파싱하는 단계로, 자바스크립트가 DOM과 스타일을 변경할 수 있기 때문에 자바스크립트 파싱과 실행이 완료되기 전까지 HTML 파싱이 중단됩니다.
4. 렌더링 (Rendering)

DOM과 CSSOM이 결합하여 생성된 Render Tree
이미지 출처 : https://web.dev/articles/critical-rendering-path/render-tree-construction?hl=ko
DOM과 CSSOM을 결합하여 화면에 렌더링 될 Render Tree를 생성합니다. 이때 Render Tree는 최종적으로 화면에 보이는 요소만을 포함합니다. HTML의 HEAD 태그, CSS에서 display:none이 적용된 요소와 같이 화면에 보이지 않는 요소는 Render Tree에 포함되지 않습니다.
5. 레이아웃 (Layout)
Render Tree의 요소들이 페이지에서 배치되는 위치와 크기, 그리고 서로 관련된 위치를 결정합니다. 이때 레이아웃 성능은 DOM의 영향을 받습니다. DOM 요소의 수가 많아질수록 레이아웃 단계에서 소요되는 시간이 길어지게 됩니다.
6. 페인팅
6-1. 페인트(Paint) 단계 : Render Tree를 분석한 후 요소들을 그룹 지어 레이어로 나눕니다. 그 후 각각의 레이어는 픽셀 데이터로 변환됩니다. 이때 각 레이어는 독립적으로 렌더링 되며 이는 브라우저의 렌더링 성능을 향상 하는데 도움을 줍니다. 레이어는 CSS 속성 (z-index, position, transform, will-change 등)과 canvas, video 태그 등을 기준으로 분리됩니다.

개발자 도구의 오른쪽 점 세 개 버튼 -> 도구 더 보기 -> 레이어 클릭 시 열리는 레이어 탭에서 레이어를 직접 확인해 볼 수 있습니다.

블로그 메인 화면 레이어

PostCard의 Image에 will-change 속성을 적용하면 이미지마다 레이어가 생성됨
시각적으로 레이어를 확인할 수 있으니 더 이해하기 쉽습니다 👍
will-change는 항상 성능에 유리할까요?
CSS 속성 중 브라우저에 미리 변경될 CSS를 알려주는 will-change가 있습니다. HTML 요소에 will-change가 적용돼 있다면 브라우저는 해당 요소를 독립적인 레이어로 분리하여 렌더링 최적화를 준비합니다. 하지만 will-change를 남발하여 너무 많은 레이어가 생성된다면 오히려 성능 저하의 원인이 됩니다. 따라서 정말 필요한 경우에만 사용하는 것을 권장합니다.
6-2. 컴포지트 (Composite) 단계: 페인트 단계에서 생성된 레이어를 결합하여 최종적으로 화면에 표시합니다.
브라우저 렌더링 최적화
브라우저는 위와 같은 과정을 통해 HTML 파일을 화면에 렌더링합니다. 그럼, 브라우저에서 렌더링을 최적화하기 위한 방법은 무엇이 있을까요?
1. 과도한 DOM 요소 사용을 지양할 것
지나치게 깊은 DOM Tree나 복잡한 DOM 요소 구조는 렌더링 성능을 저하시킬 수 있으므로 불필요한 div 태그를 사용하는 등 과도한 DOM 요소 사용은 지양하는 것이 좋습니다.
2. 리플로우와 리페인팅 최소화
포스트 초반에 인터랙션을 구현할 때 transform이 width, position 보다 성능상 유리하다는 이야기를 했습니다. 바로 CSS 속성에 따라 거치는 렌더링 단계가 달라지기 때문입니다.

DOM 요소, CSS 변경 등으로 레이아웃 과정이 다시 발생
리플로우(Reflow) 는 렌더링 과정에서 레이아웃 과정이 다시 일어나는 것을 의미합니다. DOM 요소를 추가, 제거하거나 width, position, margin 등의 CSS 속성 변경으로 인해 HTML 요소들이 재배치되어야 할 때 브라우저는 각 요소의 위치와 크기를 새로 계산하는 리플로우 과정을 거치게 됩니다. 화면 배치를 다시 계산하기 때문에 렌더링 성능 저하를 유발합니다.

레이아웃 변경 없이 CSS가 변경될 때 발생
리페인팅(Repainting) 은 렌더링 과정에서 페인팅 과정이 다시 일어나는 것을 의미합니다. 레이아웃 변화 없이 CSS가 변경될 때 리페인팅 과정을 거치게 됩니다. 리페인팅의 경우 리플로우보다는 덜 하지만 여전히 성능을 저하시키는 요인입니다. 따라서 인터랙션을 구현할 때 리플로우보다는 리페인팅을, 그 중에서도 특히 컴포지트만 발생시키는 CSS 속성을 사용하는 것이 중요합니다.
CSS Triggers에 들어가 보면 스타일 변경 시 레이아웃, 페인트, 컴포지트를 트리거하는 CSS 속성이 잘 정리되어 있습니다.


transform은 컴포지트만 트리거합니다 👍


하지만 width와 position은 레이아웃을 트리거, 즉 리플로우 단계를 거치는 것을 확인할 수 있습니다. 즉 width, position을 빈번하게 변경시키는 인터랙션이 있다면 그만큼 화면을 계산하는 일이 많아져 브라우저 렌더링 성능이 떨어지며 브라우저 화면 프레임이 뚝뚝 끊길 수도 있습니다. 그렇기에 인터랙션을 구현할 때 리플로우보다는 리페인팅을, 그중에서도 컴포지트만 발생하는 CSS 속성을 사용하는 것이 좋습니다.
레이아웃 쉬프트 (Layout Shift)
혹시 웹사이트가 로딩되며 클릭하려던 요소가 아래로 밀리는 바람에 의도하지 않은 버튼을 눌러 불쾌했던 경험 있으신가요? 그러한 현상을 레이아웃 쉬프트라고 합니다. 말그대로 레이아웃이 이동하는 현상이죠. 레이아웃 쉬프트도 리플로우를 유발하기 때문에 브라우저 렌더링 성능에 좋지 않습니다. 게다가 사용자 경험에도 좋지 않기 때문에 스켈레톤 UI를 사용하여 이를 방지하는 것이 중요합니다.
포스팅을 마치며
오늘은 브라우저의 렌더링 과정 중에서도 HTML 파일이 화면에 그려지게 되는 과정인 주요 렌더링 경로에 대하여 알아보았습니다. 예전에 공부했었지만 시간이 오래되어 자세한 내용은 기억이 살짝 흐릿한 상태였는데 이번에 포스트를 작성하며 해당 내용을 다시 정리할 수 있어 좋았습니다. 처음 주요 렌더링 경로를 알게 되었을 때 '이런 과정을 거쳐 화면이 렌더링 되는 거구나', '이래서 인터랙션 구현할 때 transform을 써야 하는구나' 하고 시야가 넓어진 기분이 들었던 기억이 납니다. 바로 이런 재미에 공부를 하게 되는 것 같습니다 😋 그럼 이번 포스트는 여기서 마무리해 보겠습니다 👋👋