본문 바로가기

FrontEnd

Critical Rendering Path 최적화하기

반응형

사용자에게 최대한 빨리 화면을 보여주고, 최대한 빨리 앱의 기능을 제공하는 방법을 알아봅시다.

우측 문서의 번역입니다. : https://web.dev/critical-rendering-path-analyzing-crp/

 

Analyzing Critical Rendering Path Performance

Learn to identify and resolve critical rendering path performance bottlenecks.

web.dev

Critical Rendering Path의 성능 병목 현상을 식별하고 해결하려면 일반적인 함정에 대한 충분한 지식이 필요합니다.
실습을 통해 페이지를 최적화하는 데 도움이 되는 일반적인 성능 패턴을 알아봅시다.
 
Critical Rendering Path를 최적화하면 브라우저가 가능한 한 빨리 페이지를 그릴 수 있습니다.
빠른 페이지는 더 많은 참여, 더 많은 페이지 조회 및 개선된 페이지 간 전환으로 이어집니다.
유저가 빈 화면을 보는 데 보내는 시간을 최소화하려면 로드되는 리소스와 순서를 최적화해야 합니다.
 
가장 간단한 경우부터 시작하여 추가 리소스, 스타일 및 애플리케이션 로직을 포함하도록 페이지를 점진적으로 작성해 보겠습니다.
이 과정에서 각 경우를 최적화합니다.
또한 쉽게 실수할 수 있는 부분을 만나게 될 것입니다.

이하 예제를 설명하기 위한 가정

  • 리소스(CSS, JS 또는 HTML 파일)를 처리할 수 있게 된 후 브라우저에서 일어나는 일에만 집중합니다.
  • 캐시나 네트워크에서 리소스를 가져오는 데 걸리는 시간은 무시합니다.
  • 서버로의 네트워크 왕복(전파 대기 시간;propagation latency) 비용(시간)은 100ms입니다.
  • 서버의 응답 시간은 HTML 문서의 경우 100ms이고 다른 모든 파일의 경우 10ms입니다.

Hello world 예제

Try it

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>Critical Path: No Style</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
  </body>
</html>​

 

간단한 HTML 마크업과 단일 이미지로 시작하겠습니다. CSS 또는 JavaScript가 없습니다.
Chrome DevTools에서 네트워크 탭을 열고 결과 리소스 폭포를 살펴보겠습니다.

주 : 최신 크롬에서는 Waterfall 탭을 찾으셔야 합니다.

이번 예제에서는 CRP를 네트워크 탭으로 설명하지만, 엄밀하게는 다른 도구를 사용해야 합니다.

(다운로드랑 실제 html을 해석해서 처리하는건 다른 이야기이기 때문)

https://web.dev/critical-rendering-path-measure-crp/

 

Measuring the Critical Rendering Path

Learn to measure the critical rendering path.

web.dev

  • html : 약 200ms
    • 네트워크 왕복에 100ms, 서버 응답 처리 시간(브라우저의 서버 응답 대기 시간) 100ms가 소요됩니다.(투명한 파란색)
      • 즉 절반은 네트워크 연결 대기, 절반은 서버 응답 대기시간입니다.
    • 용량이 4k미만으로 매우 작으므로, 한 번의 요청에 처리되며, 응답을 처리하는데 오랜 시간이 걸리지 않습니다. (진한 파란색)
  • jpg: 약 110ms
    • 네트워크 왕복 비용 100ms + 서버 응답 처리 시간(브라우저의 서버 응답 대기 시간) 10ms(투명한 보라색)
    • 용량이 4k미만으로 매우 작으므로, 한 번의 요청에 처리되며, 응답을 처리하는데 오랜 시간이 걸리지 않습니다. (진한 보라색)

아래 개념들은 해당 링크에서 확인해 주세요

  • DomContentLoaded이벤트는: DOM이 모두 준비되었고, JavaScript 실행을 차단하는 스타일시트가 없는 시점을 표시합니다.
    • 즉, 이제 (잠재적으로) 렌더 트리를 구성할 수 있음을 의미합니다.
    • 로딩 스피너를 표시하는 시점입니다.
  • load(or onload)이벤트(보라색 수직선)는 모든 리소스가 다운로드 및 처리 완료되면 발생합니다.
    • 즉, 이미지는  DomContentLoaded를 차단하지 않습니다.
    • 이미지는 로드 이벤트를 차단합니다.
    • 로딩 스피너를 멈추는 시점입니다.

위를 통해 빠른 첫번째 페인트를 제공하기 위해 모든 리소스가 필요한 것은 아님을 알 수 있습니다.

일반적으로 렌더 차단 리소스는 HTML, CSS, JS입니다.

JS와 CSS 추가하기

Try it

<!DOCTYPE html>
<html>
  <head>
    <title>Critical Path: Measure Script</title>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
  </head>
  <body onload="measureCRP()">
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="timing.js"></script>
  </body>
</html>
JavaScript 및 CSS를 추가하기 전

JavaScript 및 CSS를 추가한 후 (수직선 이동 살펴보기)

외부 CSS 및 JavaScript 파일을 추가하면 폭포수에 두 개의 요청이 추가되며, 브라우저는 두 요청을 거의 동시에 만듭니다.
이제 domContentLoaded 및 onload 이벤트 사이에 훨씬 더 작은 타이밍 차이가 있음에 유의하세요.
 

무슨일이 일어난거죠?

 

  • DOM만 필요했던 이전과 달리 CSSOM을 구성하려면 CSS 파일을 가져와 구문 분석해야 합니다
    • 렌더 트리를 빌드하려면 DOM과 CSSOM이 모두 필요합니다.
  • 이제 구문 분석을 차단하는 자바스크립트 파일이 존재합니다.
    • JavaScript를 실행하기 전에 CSS 파일이 다운로드 될 때까지 렌더링을 차단해야 합니다.
      • JavaScript가 CSSOM을 쿼리할 수 있기 때문입니다.
    • domContentLoaded 이벤트는 CSS 파일이 다운로드 및 구문 분석될 때까지 차단됩니다.
      • 즉 html > css > js 순서대로 처리한 뒤 해당 이벤트를 실행합니다.
주 : 구문 분석(parsing)차단되지 않으면 CSSOM 생성 이전에도 렌더링 할 수 있음을 의미합니다.

외부 스크립트를 인라인 스크립트로 바꾸면 어떻게 될까요?
그렇다 하더라도 브라우저는 CSSOM이 구성될 때까지 스크립트를 실행할 수 없습니다.
요컨대 인라인 자바스크립트도 파서를 차단합니다.

CSS 차단이 존재하지만, 스크립트를 인라인하면 페이지가 더 빠르게 렌더링될까요? 

 

External JavaScript:

Inlined JavaScript:

요청이 하나 줄어들었지만 onload 및 domContentLoaded 시간은 사실상 동일합니다. 왜일까요?
 
  • 브라우저가 스크립트 태그에 도달하자마자 차단하고 CSSOM이 구성될 때까지 기다리기 때문에 JavaScript가 인라인인지 외부인지는 중요하지 않습니다.
  • 또한 첫 번째 예에서 브라우저는 CSS와 JavaScript를 동시에 다운로드하고 거의 동시에 다운로드를 완료합니다.
이 경우 JavaScript 코드를 인라인하는 것은 별로 도움이 되지 않습니다.
 
그러나 페이지를 더 빠르게 렌더링할 수 있는 몇 가지 전략이 있습니다.
 
먼저 모든 인라인 스크립트는 파서 차단이지만
외부 스크립트의 경우 "async" 키워드를 추가하여 파서 차단을 해제할 수 있습니다. 
주 : 해당 스크립트가 CSS를 만질 필요가 없는 경우)
 
Try it
<!DOCTYPE html>
<html>
  <head>
    <title>Critical Path: Measure Async</title>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
  </head>
  <body onload="measureCRP()">
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script async src="timing.js"></script>
  </body>
</html>
파서 차단(외부) JavaScript:

비동기(외부) JavaScript:

훨씬 낫군요!

domContentLoaded 이벤트는 HTML이 구문 분석된 직후에 발생합니다.

브라우저는 JavaScript에서 차단하지 않는다는 것을 알고 있습니다.

다른 파서 차단 스크립트가 없기 때문에 CSSOM 구성도 병렬로 진행할 수 있습니다.

또는 CSS와 JavaScript를 모두 인라인할 수 있습니다.
<!DOCTYPE html>
<html>
  <head>
    <title>Critical Path: Measure Inlined</title>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <style>
      p {
        font-weight: bold;
      }
      span {
        color: red;
      }
      p span {
        display: none;
      }
      img {
        float: right;
      }
    </style>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script>
      var span = document.getElementsByTagName('span')[0];
      span.textContent = 'interactive'; // change DOM text content
      span.style.display = 'inline'; // change CSSOM property
      // create a new element, style it, and append it to the DOM
      var loadTime = document.createElement('div');
      loadTime.textContent = 'You loaded this page on: ' + new Date();
      loadTime.style.color = 'blue';
      document.body.appendChild(loadTime);
    </script>
  </body>
</html>

domContentLoaded 시간은 이전 예와 사실상 동일합니다.
JavaScript를 비동기로 표시하는 대신 CSS와 JS를 모두 페이지 자체에 인라인했습니다.
이렇게 하면 HTML 페이지가 훨씬 더 커지지만
장점은 브라우저가 외부 리소스를 가져오기 위해 기다릴 필요가 없습니다.
(4kb 미만이면 좋을 듯...)
 
이와 같이 간단한 페이지에서도 중요한 렌더링 경로를 최적화하는 것은 쉬운 일이 아닙니다.
  • 서로 다른 리소스 간의 종속성 그래프를 이해해야 하고,
  • 어떤 리소스가 "cricical"한지 식별해야 하며,
  • 페이지에 해당 리소스를 포함하는 방법에 대해 다양한 전략 중에서 선택해야 합니다.

이 문제에 대한 단 하나의 해결책은 없습니다. 페이지마다 다릅니다.
최적의 전략을 파악하려면 유사한 프로세스를 직접 따라야 합니다.

즉, 한발 물러서서 몇 가지 일반적인 성능 패턴을 식별할 수 있는지 봅시다.


성능 패턴

Critical Rendering Path 설명을 위한 어휘 : 중요 경로 특성

  • Critical Resource: 페이지의 초기 렌더링을 차단할 수 있는 리소스입니다.
  • Critical Path Length: 네트워크 왕복 횟수 혹은 모든 중요 리소스를 가져오는 데 필요한 총 시간입니다.
  • Critical Bytes: 모든 중요 리소스 파일 크기의 합계입니다.
    • 페이지의 첫 번째 렌더링에 필요한 총 바이트 수입니다.
  • 일반적인 렌더링 차단 리소스는 HTML 마크업, CSS 및 JavaScript입니다.
    • 이미지는 렌더링 패스를 차단하지 않습니다만, 빨리 그릴수록 좋습니다.
    • Javascript와 CSS는 특정 힌트를 통해 렌더링 차단하지 않도록 할 수 있습니다.
 

가장 간단한 페이지 : HTML 마크업으로만 구성

CSS, JavaScript 또는 기타 유형의 리소스가 없습니다.
이 페이지를 렌더링하려면 브라우저가 요청을 시작하고 HTML 문서가 도착할 때까지 기다렸다가 구문 분석하고 DOM을 빌드한 다음
마지막으로 화면에 렌더링 합니다.
 
Try it
<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>Critical Path: No Style</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
  </body>
</html>

  • T0과 T1 사이의 시간은 네트워크 및 서버 처리 시간을 의미합니다.
  • 가장 좋은 경우(HTML 파일이 작은 경우) 한 번의 네트워크 왕복으로 전체 문서를 가져옵니다.
    • TCP 전송 프로토콜이 작동하는 방식으로 인해 파일이 클수록 더 많은 왕복이 필요할 수 있습니다.

 

HTML 마크업 + 외부 CSS

Try it
<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
  </body>
</html>
다시 한 번, HTML 문서를 가져오기 위해 네트워크 왕복이 발생하고 다운로드한 마크업은 CSS 파일도 필요하다고 알려줍니다.
이는 브라우저가 서버로 돌아가서 화면에 페이지를 렌더링하기 전에 CSS를 가져와야 함을 의미합니다.
결과적으로 이 페이지는 표시되기 전에 최소 두 번의 왕복이 발생합니다.
하나의 CSS 파일을 다운로드하기 위해 네트워크를 여러 번 왕복할 수 있으므로 "최소"에 중점을 둡니다.
 
이제 위의 HTML + CSS 예제의 중요 경로 특성과 비교해 보겠습니다.
  • 2 critical resources
  • 2 or more roundtrips for the minimum critical path length
  • 9 KB of critical bytes
렌더 트리를 구성하려면 HTML과 CSS가 모두 필요합니다.
결과적으로 HTML과 CSS는 모두 중요한 리소스입니다.
CSS는 브라우저가 HTML 문서를 가져온 후에만 가져오므로 주요 경로 길이는 최소 2번의 왕복입니다.
두 리소스는 총합 9KB의 주요 바이트를 추가합니다.
 
JavaScript 파일을 마지막으로 추가해 보겠습니다.
 
<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="app.js"></script>
  </body>
</html>

 

페이지의 외부 JavaScript 에셋이자 파서 차단(즉, 중요한) 리소스인 app.js를 추가했습니다.
설상가상으로 JavaScript 파일을 실행하려면 CSSOM 생성이 완료될 때까지 기다려야 합니다.
JavaScript는 CSSOM을 쿼리할 수 있기 때문입니다.
style.css가 다운로드되고 CSSOM이 구성될 때까지 브라우저가 일시 중지됩니다.
즉, 실제로 이 페이지의 "네트워크 폭포수"를 보면 CSS와 JavaScript 요청이 거의 동시에 시작된다는 것을 알 수 있습니다.
브라우저는 HTML을 가져와서 처리하며, 처리 과정에서 두 리소스를 모두 발견한 후, 두 요청을 동시에 시작합니다.
결과적으로 위 페이지는 다음과 같은 주요 경로 특성을 갖습니다.
  • 3 critical resources
  • 2 or more roundtrips for the minimum critical path length
  • 11 KB of critical bytes
이제 최대 11KB의 중요 바이트를 추가하는 3개의 주요 리소스가 있지만
CSS와 JavaScript를 병렬로 전송할 수 있기 때문에 중요 경로 길이는 여전히 2회 왕복입니다.
주요 렌더링 경로의 특성을 파악한다는 것은
주요한 리소스를 식별하고 브라우저가 가져오기를 스케줄링 하는 방법을 이해할 수 있다는 것을 의미합니다.
 
예제를 계속 진행해 보겠습니다.
 
페이지에 포함된 JavaScript를 차단할 필요가 없다는 것을 깨달았습니다.
페이지 렌더링을 차단할 필요가 없는 일부 분석용 및 기타 코드가 있습니다.
이러한 지식을 바탕으로 스크립트 태그에 "async" 속성을 추가하여 파서를 차단 해제할 수 있습니다.
Try it
<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="app.js" async></script>
  </body>
</html>

(주 : DOM을 만들기 위해선 HTML만 있으면 됨. CSS는 CSSOM을 위해 필요함.)

비동기 스크립트에는 다음과 같은 몇 가지 장점이 있습니다.
  • 스크립트는 더 이상 파서 차단이 아니며 중요한 렌더링 경로의 일부가 아닙니다.
  • 다른 주요 스크립트가 없기 때문에 CSS는 domContentLoaded 이벤트를 차단할 필요가 없습니다.
  • domContentLoaded 이벤트가 더 빨리 실행될수록 다른 애플리케이션 로직을 더 빨리 실행할 수 있습니다.

최적화된 페이지는 이제

두 개의 주요 리소스(HTML 및 CSS)가 존재하며,
최소 주요 경로 길이는 두 번 왕복이고,

총 9KB의 주요 바이트를 사용합니다.

 

마지막으로 CSS 스타일시트가 print(종이 인쇄)용으로만 필요하다면?
<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" media="print" />
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="app.js" async></script>
  </body>
</html>

style.css 리소스는 프린트(인쇄)에만 사용되기 때문에 브라우저는 페이지를 렌더링하기 위해 이를 차단할 필요가 없습니다.
따라서 DOM 구성이 완료되자마자 브라우저는 페이지를 렌더링하기에 충분한 정보를 갖게 됩니다.
결과적으로 이 페이지에는 하나의 주요 리소스(HTML 문서)만 있고 최소 중요 렌더링 경로 길이는 1회 왕복입니다.

 

참고 :

https://ui.toast.com/fe-guide/ko_PERFORMANCE

 

 

반응형