Critical Rendering Path - 2

2020년 05월 25일

👉전편에서는 크리티컬 렌더링 패스의 전반적인 과정에 대해 설명. 이번에는 최적화 하는 방법과 JavaScript에 대해 조금 더 자세히 포스팅할 예정이다.

TL;DR

크리티컬 렌더링 패스를 최적화하는 데에 크게 3가지의 방법이 있다.

  1. Minify, Compress, Cache: HTML, CSS, JS
  2. Minimize CSS blocking

    • Inline CSS
    • Split file with media types and queries
  3. Minimize JS blocking

    • Use async attribute with script tag
    • Defer JavaScript execution

Details

Minify, Compress, Cache

이것은 HTML, CSS, JavaScript 모두에게 해당된다. 외부 파일로 요청할 경우 파일을 받아올 때 당연히 파일 크기를 작게 만들어야 전송 시간을 줄일 수 있다. 그러므로 코드의 공백을 없애서 Minify 하고, 압축하고, 캐싱해서 브라우저가 파일을 다운로드하는 시간을 줄인다.

Minimize CSS blocking

CSS는 render blocking이다. HTML은 partial DOM 트리를 사용할 수 있으며 일부만 화면에 먼저 렌더링하는 것이 가능하다. 하지만 CSS는 Cascading되는 속성이 있기 때문에 전체 트리가 완성될 때까지 사용할 수 없다. 따라서 CSSOM이 빌드되기 전까지 브라우저는 화면 렌더링을 막는다. 이러한 부분을 최소화해야 렌더링 속도를 빠를게 할 수 있을 것이다.

이를 위해서 먼저 inline css를 사용할 수 있다. 모든 스타일을 inline으로 처리하기는 어렵겠지만, 일부 우선적으로 렌더링되어야 할 부분이 있다면 inline으로 처리할 수 있을 것이다. 외부 파일로 링크를 걸어놓을 때에는 css 파일을 요청하고 응답받는 시간이 걸리지만, inline일 경우 요청할 필요가 없기 때문에 최적화에 도움이 된다.

그리고 media type과 query를 사용하는 방법이 있다.

<link href="print.css" rel="stylesheet" media="print" />
<link href="other.css" rel="stylesheet" media="(min-width: 40em)" />

첫 번째 링크는 미디어 타입이 print로 되어 있어 프린트할 때에만 필요한 스타일시트이다. 그러므로 스크린에서 일반적으로 렌더링할 때에는 실행할 필요가 없으므로 렌더링이 블로킹되지 않는다.

두 번째 링크는 미디어 쿼리가 있어서 조건이 일치할 때에만 그 스타일이 필요하므로, 해당 조건이 일치하지 않을 때에는 브라우저가 렌더링을 블로킹하지 않는다. 단, 조건이 일치할 때에는 해당 스타일시트를 다운로드하여 빌드가 완성될 때까지 렌더링을 블로킹한다.

이렇게 미디어 타입과 미디어 쿼리를 사용하여 css 파일을 쪼개어 링크로 다는 것은 용도에 따라 불필요한 블로킹을 방지할 수 있어 최적화에 도움이 된다.

Minimize JavaScript blocking

자바스크립트가 어떻게 render blocking이 되는지 예시를 들어 보자.

<p>
  Go Jeju
  <script>
    document.write(' with Leo ')
  </script>
  is awesome
</p>

위 코드를 실행하면 Go Jeju with Leo is awesome 이라는 텍스트가 스크린에 나타날 것이다. 이 코드의 실행 과정을 설명하자면, 우선 HTML을 파싱하며 DOM이 점진적으로 생성되던 중 스크립트 태그를 만나면 파싱이 중단되고 자바스크립트를 실행한다. 즉 자바스크립트는 DOM 생성을 방해하는 parser blocking이다.

<p>
  Go Jeju
  <script src="write.js"></script>
  is awesome
</p>

만약 inline으로 삽입하지 않고 이렇게 write.js 라는 파일로 분리해 두었다면 어떨까. 파일에 똑같은 코드가 쓰여있다고 할지라도 외부 자바스크립트 파일을 요청하고 다운로드하는 과정에서 더욱 시간이 걸린다.(CSS도 마찬가지다)

<style src="style.css"></style>
<p>
  Go Jeju
  <script>
    var e = document.getElementByTagName('p')[0]
    e.style.color = 'red'
  </script>
  is awesome
</p>

그리고 자바스크립트는 DOM 파싱을 막을 뿐만 아니라 CSSOM에 의존적이다. 만약 style.css에서 p태그의 폰트 컬러를 black으로 선언하고 스크립트 태그 내에서는 red로 바꾸었다면, 컬러는 결국 어떻게 나타날까?

컬러는 red로 나타난다. 왜냐하면 자바스크립트가 실행되는 중 스타일 속성을 만나면 CSSOM 빌드가 끝날 때까지 기다리기 때문이다. 전체적인 순서는 이렇다.

  1. html을 요청한다 -> 응답이 오면 html을 파싱하고 DOM을 만들기 시작한다
  2. (1) DOM을 만들다가 CSS를 발견하면 CSS를 요청한다 -> 응답이 오면 CSSOM을 만들기 시작한다

    (2) DOM을 만들다가 script 태그를 만나면 파싱을 중지하고 자바스크립트를 실행한다 -> 자바스크립트에서 스타일을 조작하려 하므로 CSSOM이 만들어질 때까지 기다린다.

  3. CSSOM이 완성된다
  4. JavaScript가 실행된다
  5. 나머지 html 파싱이 계속된다

결국 CSS는 render blocking일 뿐만 아니라 자바스크립트 실행도 막으므로 CSS를 최적화하는 게 중요하다. 또한 자바스크립트가 HTML 파싱을 막는 것을 방지하는 것도 중요하다.

그 방법으로는 첫 번째로 async 속성을 스크립트 태그에 선언하는 것이 있다.

<script src="analytics.js" async></script>

async를 명시하면 브라우저에게 이 스크립트 파일을 요청하되, HTML 파싱을 계속하게 할 수 있다. 즉 파싱을 막지 말고 HTML을 계속 파싱하라고 브라우저에게 알려주는 것이다.

두 번째로는 onload 이벤트를 사용하여 스크립트를 HTML과 CSS 파싱이 모두 끝난 다음에 실행하는 것이다. 그러면 중간에 파싱이 막혀 불필요하게 왕복할 필요가 없어진다.