ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 렌더링 최적화 방법 - 초기 렌더링 최적화
    개발자 이야기 2021. 6. 28. 10:49
    프론트엔드 성능 최적화 = 로딩 최적화 + 렌더링 최적화

     

    성능 최적화를 통해 빠른 로딩 속도가 구현된다면 사용성 개선과 더불어 사업지표 향상에도 도움된다.

    이번 포스트에서는 로딩 최적화와 렌더링 최적화 중에서도 렌더링 최적화를 소개하려 한다.

    렌더링 최적화는 크게 초기 렌더링 최적화와 렌더링 업데이트 최적화로 나뉘는데, 이 중 '초기 렌더링 최적화'에 대해서 정리해 보았다.

     

    초기 렌더링 최적화 방법

    Critical Rendering Path 최적화

    Critical Rendering Path(중요 렌더링 경로, CRP)

    • 브라우저가 페이지의 초기 출력을 위해 실행해야 하는 순서(브라우저가 HTML, CSS, Javascipt를 화면에 픽셀로 변화하는 일련의 단계)로, 이를 최적화하는 것은 렌더링 성능을 향상시킨다.

    Critical Rendering Path 최적화란?

    • HTML, CSS, JavaScript 간의 종속성을 이해하고 최적화하는 것을 말한다.

    Critical Rendering Path 진행 과정 확인

    • Chrome에서는 DevTools의 Performance에서 확인이 가능하다.

    <html>  
      <head>  
        <title>Understanding the Critical Rendering Path</title>
        <link rel="stylesheet" href="style.css">
      </head>  
      <body>  
        <header>
          <h1>Understanding the Critical Rendering Path</h1>
        </header>
        <main>
          <h2>Introduction</h2>
          <p>Lorem ipsum dolor sit amet</p>
        </main>
        <footer>
          <small>Copyright 2017</small>
        </footer>
        <script src="main.js"></script>
      </body>  
    </html>  

     

     

     

    • Send Request
      - index.html에 대한 GET 요청을 전송한다.
    • Parse HTML & Send Request
      - 전달 받은 HTML을 파싱한다.
      - style.css와 main.js에 대한 GET 요청을 전송한다.
    • Parse Stylesheet
      - CSS를 파싱한다.
    • Evaluate Script
      - 전달 받은 main.js를 실행한다.
    • Layout
      - HTML의 메타 뷰포트 태그를 기반으로 레이아웃을 배치한다.
    • Paint
      - 화면에 페인트한다.

     

     

     

    CSS 최적화

    CSS는 렌더링 차단 리소스(render blocking resource)다. 즉, CSSOM이 생성될 때까지 브라우저는 렌더링하지 않는다.

    최초 렌더링에 걸리는 시간을 최적화하기 위해서는 CSS를 간단하게 만들고 클라이언트에 최대한 빠르게 다운로드 되어야 한다.

     

    • 간결한 selector 사용
      - 덜 구체적인 선택자는 더 구체적인 선택자보다 더 빠르다.
      - 예를 들어, 브라우저가 .foo 찾을 때, .foo {} .bar .foo {} 보다 빠르다.
      - 왜냐하면 .bar .foo {} 의 경우, .foo가 부모 객체인 .bar를 가지고 있는지 확인하기 위해 DOM을 거슬러 올라가기 때문이다.
      <div class="container">
        <ul class="list">
          <li>
            <button type="button" class="btn">버튼</button>
          </li>
          <li>
            <button type="button" class="btn">버튼</button>
          </li>
        </ul>
      </div>
      // bad 
      .container .list li .btn {
        background-color: red; 
      } 
      
      // good 
      .list .btn { 
        background-color: red; 
      }
    • 미디어 유형과 미디어 쿼리 사용
      <link rel="stylesheet" href="style.css" /> // blocking
      <link rel="stylesheet" href="style.css" media="all" /> // blocking, 미디어 유형 사용
      <link rel="stylesheet" href="print.css" media="print" /> // non blocking, 미디어 유형 사용
      <link rel="stylesheet" href="portrait.css" media="orientation: landscape" /> 
      // non blocking, 미디어 쿼리 사용으로 기기의 방향이 가로일 때만 렌더링 차단
      <link rel="stylesheet" href="mobile.css" media="screen and (max-width: 480px)" /> 
      // non blocking on large screens, 미디어 쿼리 사용으로 기기의 너비 조건이 일치 시 렌더링 차단

      - 페이지 인쇄 or 모바일 출력의 경우 등 특정 조건에 사용되는 CSS가 있다면 미디어 유형과 미디어 쿼리를 사용해 CSS 리소스를 렌더링 비차단 리소스로 표시할 수 있다.
      - 미디어 쿼리(media) 기반으로 CSS를 여러 파일로 분할하면, 사용하지 않는 CSS를 다운로드만 하고 렌더링을 차단하진 않는다.

     

    JavaScript 최적화

    • 자바스크립트 파서 차단 리소스(parser blocking resource)다.
    • 웹은 파싱과 실행이 동시에 수행되는 동기화(synchronous) 모델이기 때문에 <script> 태그를 만나면 진행하던 HTML 파싱을 중지하고 자바스크립트 엔진에게 권한을 넘겨 자바스크립트를 다운, 파싱, 실행한다.
    • <script> 태그 다운, 파싱  >>  <script> 태그 실행  >>  실행 완료 후 다음 태그 파싱
    • 이러한 이유로, 일반적으로 <body> 태그를 닫기 직전에 <script> 태그를 선언한다.
    • HTML5에서는 <script> 태그를 비동기적으로 처리하는 속성(async, defer)이 추가되었다.

       

    1) <head> 안에 <script>가 위치해 있는 경우

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <title>Document</title>
        <script src="main.js"></script>
      </head>
      <body></body>
    </html>

     

    • 파싱 중 <script> 태그를 만나면 html 파싱을 멈추고 해당 자바스크립트 파일(main.js)을 서버에서 다운받아 곧바로 실행한다.
    • 단점 : 실행된 파일이 완료될 때까지 html 파싱이 멈추기 때문에, 자바스크립트 파일의 크기가 크거나 인터넷이 느린 상황일 경우 사용자가 웹사이트를 보는데까지 많은 시간이 걸린다.

       

    2) <body> 안에, 끝부분에 <script>가 위치해 있는 경우

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <title>Document</title>
      </head>
      <body>
        <div></div>
        <script src="main.js"></script>
      </body>
    </html>

     

    • <body> 안에서도 끝부분에 위치해 있기 때문에 사용자가 기본적인 html 콘텐츠를 상대적으로 빨리 불 수 있다.
    • 단점 : 자바스크립트에 많이 의존하는 웹사이트의 경우 

       

    3) <head> 안에 <script> 위치 + async 속성 사용

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <title>Document</title>
        <script async src="main.js"></script>
      </head>
      <body></body>
    </html>

     

    • async는 boolean 타입의 속성값이기 때문에 선언하는 것만으로도 true로 설정된다.
    • 파싱 중 async 속성을 가진 <script> 태그를 만나면 병렬적으로 자바스크립트 파일을 다운받게 된다.
    • 즉, 자바스크립트 파일 다운이 완료되기 전까지 HTML 파싱을 계속 진행하다가 다운이 완료되면 파싱을 멈추고 다운된 자바스크립트 파일을 실행한다.
    • 장점 : fetching이 병렬적으로 진행되기 때문에 이전에 소개한 방법들 보단 다운로드 받는 시간 절약 가능
    • 단점 : 페이지 준비 시간이 상대적으로 절약되긴 하지만 여전히 자바스크립트 파일 실행 중에는 HTML 파싱이 멈추게 된다.
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <title>Document</title>
        <script async src="aaa.js"></script>
        <script async src="bbb.js"></script>
        <script async src="ccc.js"></script>
      </head>
      <body></body>
    </html>

     

    • async 속성을 가진 다수의 script를 다운 받게 되면, 먼저 다운받은 script를 우선적으로 실행한다.
    • bbb.js  >>  aaa.js  >>  ccc.js
    • 단점 : 웹사이트의 자바스크립트가 순서에 의존적인 경우, 원하는 순서로 자바스크립트 파일을 조작이 어렵다.

       

    4) <head> 안에 <script> 위치 + defer 속성 사용 -> 가장 선호하는 방법

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <title>Document</title>
        <script defer src="main.js"></script>
      </head>
      <body></body>
    </html>

     

    • async와 같이, 파싱 중 defer 속성을 가진 <script> 태그를 만나면 병렬적으로 자바스크립트 파일을 다운 받게 된다.
    • async와 다른 점은 HTML 파싱이 다 마친 후 자바스크립트 파일을 실행한다는 점이다.
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <title>Document</title>
        <script defer src="aaa.js"></script>
        <script defer src="bbb.js"></script>
        <script defer src="ccc.js"></script>
      </head>
      <body></body>
    </html>

     

    • defer 속성을 가진 다수의 script를 다운 받게 되면 async와는 다르게, 다운 받는 순서와 상관 없이 파싱 순서에 따라 실행하기 때문에 원하는 순서에 맞게 조작이 가능하다.

     

    언제 async를 사용하면 될까?

    • 스크립트 파일에 종속성이 없는 경우
    • async는 스크립트 리소스가 다운로드 완료되면 HTML 파싱을 중단하고 스크립트가 실행되기 때문에, 어느 시점에 HTML 파싱이 중단될지 보장할 수 없다.
    • 그러므로 DOM에 종속성이 없어서 HTML 파싱 중 어느 시점에 스크립트가 실행되든지 상관없을 경우 async 속성을 사용하는 것이 좋다.

     

    언제 defer를 사용하면 될까?

    • async와 반대로 종속성이 있는 경우
    • DOM과 종속성이 있어서, DOM이 전부 생성되어야 정상적인 동작을 할 수 있는 경우
    • 다수의 스크립트 간에 종속성이 있어서, 스크립트 실행 순서가 항상 <script> 정의 순서로 실행되어야 하는 경우

     

    리소스 우선순위 지정

    브라우저에게 리소스 우선순위 전달함으로써 최적화할 수 있다.

     

    preload 속성

    • 현재 페이지에서 빠르게 가져와야 하는 리소스에 사용되는 속성이다.
    <link rel="preload" as="script" href="main.js">
    <link rel="preload" as="style" href="style.css">
    • 주의사항
      - as 속성을 사용하여 리소스의 유형을 브라우저에게 알려줘야 한다.
      - preload 속성은 리소스를 반드시 가져오기 때무에 리소스가 중복되지 않도록 해야 한다.
      - 현재 페이지에서 반드시 사용되는 리소스에만 사용해야 한다.

    • 브라우저 지원 현황

    prefetch 속성

    • 현재 페이지 로딩이 마친 후 사용 가능한 대역폭이 있을 때(다운로드할 여유가 생겼을 때) 가장 낮은 우선순위로 리소스를 가져온다.
    • 브라우저는 미래에 사용될 리소스들을 가져와 캐시에 저장한다.
    <link rel="prefetch" href="subPage.html">
    • 주의사항
      - 위의 코드가 동작했을 경우, subPage.html 리소스는 가져오지만 subPage.html에서 사용되는 CSS 등의 리소스들은 가져오지 않는다.

    • 브라우저 지원 현황

     

    요약

    초기 렌더링 최적화 

    브라우저가 페이지의 초기 출력을 위해 실행해야 하는 순서인 CRP를 최적화함으로써 초기 렌더링을 최적화할 수 있다.

    • CSS 최적화 : 미디어 유형, 미디어 쿼리 사용
    • JavaScript 최적화 : <body> 태그 닫기 직전에 <script> 태그를 선언하거나 async, defer 속성을 사용한다.
    • preload, prefetch 속성을 사용하여 리소스의 우선순위를 지정한다.

     

     

    Reference

    '개발자 이야기' 카테고리의 다른 글

    브라우저 렌더링  (0) 2021.06.25

    댓글

Designed by Tistory.