웹사이트 성능 최적화 (2) Rendering
앞선 포스팅에서 웹 사이트 요청 후 첫번째 응답인 TTFB를 빠르게 하는 방법에 대해 알아 보았습니다. 이 과정을 통해 HTML 및 여러 데이터를 응답받고 브라우저에서 렌더링 과정이 시작됩니다.
브라우저는 점진적으로 렌더링을 한다.
브라우저에서 웹 페이지를 렌더링하는 것은 데이터가 있는 웹사이트에 의존할 수 밖에 없기 때문에 점진적으로 렌더링할 수 있도록 설계되었습니다.
일반적으로 페이지를 렌더링하기 위한 리소스가 있을 때 렌더링 과정이 시작됩니다.
하지만 이 렌더링 과정에는 아래와 같은 고민점들이 있고, 사용자 경험 향상을 위해 전략을 선택할 필요가 있습니다.
- HTML만 먼저 렌더링하면 페이지가 손상되어 보이며 최종 렌더링 시 상당히 달라져 보이게 됩니다.
- 최종 렌더링을 위한 모든 리소스를 렌더링하면 사용자가 많은 시간을 기다려야 합니다.
이 때문에 브라우저는 명백히 손상된 환경을 제공하지 않도록 하려면 기다려야 하는 최소 리소스 수를 알아야 합니다. 반면 브라우저는 사용자에게 콘텐츠를 표시하기 전에 필요한 시간보다 오래 기다리면 안 됩니다. 초기 렌더링을 실행하기 전에 브라우저가 실행하는 단계를 주요 렌더링 경로라고 합니다.
주요 렌더링(Critical Rendering) 경로
- HTML에서 문서 객체 모델 (DOM) 생성
- CSS에서 CSS 개체 모델 (CSSOM) 생성
- DOM 또는 CSSOM을 변경하는 모든 자바스크립트 적용
- DOM 및 CSSOM에서 렌더링 트리 생성
- 페이지에서 스타일 및 레이아웃 작업을 실행하여 어떤 요소가 적절한지 확인합니다.
- 메모리에 있는 요소의 픽셀을 페인팅합니다.
- 픽셀이 겹치는 경우 이를 합성합니다.
- 모든 결과 픽셀을 화면에 물리적으로 그립니다.
초기 렌더링 과정에 포함된 리소스
<head>
의 요소를 관리하는 것이 렌더링 최적화의 핵심이라고 할 수 있겠습니다.
- 일부 HTML
<head>
요소의 렌더링 차단 CSS<head>
요소의 렌더링 차단 JavaScript
그러나 <head>
요소에서 참조된 모든 리소스가 초기 페이지 렌더링에 반드시 필요한 것은 아니므로 브라우저는 리소스가 있는 리소스만 기다립니다. 어떤 리소스가 주요 렌더링 경로에 있는지 식별하려면 렌더링 차단 및 파서 차단 CSS 및 자바스크립트를 이해해야 합니다.
초기 렌더링 과정을 막지 않지만 고려해야 할 리소스
폰트
와 이미지
는 채워지는 콘텐츠로 간주되는 경우가 많기 때문에 충분한 공간이 HTML을 통해 예약되지 않는다면 빈 공간영역이 남거나 콘텐츠가 로드될 때 페이지 레이아웃이 변경될 수 있어 레이아웃 변경횟수(CLS)에 영향을 미칩니다.
렌더링을 차단하는 리소스
CSS가 기본적으로 이 카테고리로 분류됩니다.
리소스가 렌더링을 차단한다고 해서 브라우저가 다른 작업을 하지 못하게 되는 것은 아닙니다. 브라우저는 최대한 효율성을 높이려고 합니다. 따라서 브라우저가 CSS 리소스를 다운로드해야 한다고 판단하면 요청하고 렌더링을 일시중지하지만 HTML의 나머지 부분을 계속 처리하고 그동안 할 다른 작업을 찾습니다. 최근의 혁신 기능은 Chrome 105에 추가된 [blocking=render
속성](https://html.spec.whatwg.org/multipage/urls-and-fetching.html#blocking-attributes)입니다. 이를 통해 개발자는 요소가 처리될 때까지 <link>
, <script>
또는 <style>
요소를 렌더링 차단으로 명시적으로 표시하지만 그동안 파서가 계속해서 문서를 처리하도록 할 수 있습니다.
브라우저에서 CSS(<style>
요소의 인라인 CSS 또는 <link rel=stylesheet href="...">
요소에 의해 지정된 외부 참조 리소스 등)를 인식하면 브라우저에서 해당 CSS의 다운로드와 처리를 완료할 때까지 더 이상 콘텐츠를 렌더링하지 않습니다.
CSS는 기본적으로 렌더링을 차단하지만, 현재 조건(<link rel=stylesheet href="..." media=print>
)과 일치하지 않는 값을 지정하도록 <link>
요소의 media
속성을 변경하여 렌더링을 차단하지 않는 리소스로 전환할 수 있습니다.
파서를 차단하는 리소스
JavaScript가 기본적으로 이 카테고리로 분류됩니다.(async
, defer
로 선언되지 않은 경우)
파서 차단 리소스는 HTML을 계속 파싱하여 브라우저가 실행할 다른 작업을 찾지 못하게 하는 리소스입니다. JavaScript는 실행 시 DOM 또는 CSSOM을 변경할 수 있기 때문에 파서를 차단 리소스로 분류합니다.
파서 차단 리소스는 사실상 렌더링 차단이기도 합니다. 파서는 완전히 처리될 때까지 파싱 차단 리소스를 지나 계속할 수 없으므로 그 이후의 콘텐츠에 액세스하여 렌더링할 수 없습니다. 브라우저는 대기하는 동안 지금까지 수신된 HTML을 렌더링할 수 있지만 중요한 렌더링 경로와 관련된 경우 <head>
의 파서 차단 리소스는 사실상 모든 페이지 콘텐츠의 렌더링이 차단되었음을 의미합니다.
파서를 차단하면 렌더링을 차단하는 것 이상의 성능 비용이 발생할 수 있습니다. 따라서 브라우저에서는 기본 HTML 파서가 차단된 동안 향후 리소스를 다운로드하기 위해 Preload Scanner라고 하는 보조 HTML 파서를 사용하여 비용을 절감합니다. 실제로 HTML을 파싱하는 것만큼 좋지는 않지만 최소한 브라우저의 네트워킹 함수가 차단된 파서보다 먼저 작동하도록 합니다. 즉, 나중에 다시 차단될 가능성이 낮습니다.
콘텐츠가 포함된 주요 렌더링 경로
주요 렌더링(Critical Rendering) 경로는 오랫동안 초기 렌더링과 관련이 있었습니다.
웹 성능에 관한 보다 사용자 중심 측정항목이 등장하여 주요 렌더링 경로의 끝점이 첫 번째 페인트(FCP)여야 하는지 아니면 이후 후속 페인트 중 더 콘텐츠가 많은 페인트(LCP) 중 하나여야 하는지 의문을 가지고 있습니다.
또 다른 관점으로 최대 콘텐츠 렌더링 시간 (LCP) 또는 첫 콘텐츠 페인트 (FCP)까지 걸리는 시간에 집중하는 것입니다. 이 경우 주요 렌더링 경로의 일반적인 정의와 마찬가지로 반드시 차단하지는 않지만 콘텐츠가 포함된 페인트를 렌더링하는 데 필요한 리소스를 포함해야 할 수 있습니다.
‘중요’로 정의한 정확한 정의와 관계없이 초기 렌더링과 주요 콘텐츠를 유지하는 요소를 이해하는 것이 중요합니다. 첫 번째 페인트는 사용자를 위해 모든 것을 렌더링할 수 있는 첫 번째 기회를 측정합니다. 이상적으로는 배경 색상과 같은 것이 아니라 의미 있는 값이어야 합니다. 하지만 콘텐츠가 포함되어 있지 않더라도 무언가를 표시하는 데는 여전히 가치가 있습니다.