블로그 성능 개선 (리소스 로딩)

작성일: 2023-10-23

Prerequisite

  • 블로그 성능 분석

이번에는 리소스 로딩에 따른 Rednering Blocking 개선을 진행해보겠습니다.

현재 블로그의 리소스 로딩 순서는 아래와 같습니다.

_includes/head.html

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=Edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="google-site-verification" content="3gB9dKJy3OEOfBDFjyygUv7UEtbCLSaSsj1iRgGMLqM" />
  <meta name="naver-site-verification" content="3c1362974d453b6fe1fdd5089325e29980b1369e" />

  {% include_cached favicon.html %}

  <link id="main-css" rel="stylesheet" href="{{ '/assets/css/just-the-docs-light.css' | relative_url }}">
  <script src="{{ '/assets/js/vendor/lunr.min.js' | relative_url }}"></script>
  <script src="{{ '/assets/js/vendor/lunr.stemmer.support.min.js' | relative_url }}"></script>
  <script src="{{ '/assets/js/vendor/lunr.multi.min.js' | relative_url }}"></script>
  <script src="{{ '/assets/js/vendor/lunr.ko.min.js' | relative_url }}"></script>
  <script src="{{ '/assets/js/fslightbox.js' | relative_url }}"></script>
  <script src="{{ '/assets/js/lazysizes.min.js' | relative_url }}"></script>
  <script src="{{ '/assets/js/just-the-docs.js' | relative_url }}"></script>

  {%- if site.ga_tracking != nil %}
  {% assign ga_tracking_ids = site.ga_tracking | split: "," %}
  <script src="https://www.googletagmanager.com/gtag/js?id={{ ga_tracking_ids.first }}"></script>
  {%- endif %}
  {% seo %}
</head>

다행히 리소스 파일 사이즈가 작아 빠르게 불러오지만 lighthouse 분석 결과 문제가 된다고 알려주고 있습니다.

defer

  • 스크립트 로드가 완료되어도 HTML 파싱 완료 후에 스크립트 실행
  • 스크립트 태그가 여러개 있으면 선언한 순서대로 실행

async

  • 스크립트 로드 완료 후 HTML 파싱이 중단되고 스크립트 실행
  • 스크립트 태그가 여러개 있으면 로드한 순서대로 실행 (순서를 보장할 수 없음)

저의 블로그 특성 상 HTML 파싱이 완료된 후에 스크립트가 실행되어도 상관없어 스크립트에 defer 속성을 추가하여 HTML 파싱 후 스크립트가 실행되도록 변경했습니다.

추가로 google tag manager 스크립트는 tag 생성 로직이 함께 포함되야 하므로 가독성을 위해 just-the-docs 스크립트 안에 넣어서 함께 처리했습니다.

_includes/head.html

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=Edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="google-site-verification" content="3gB9dKJy3OEOfBDFjyygUv7UEtbCLSaSsj1iRgGMLqM" />
  <meta name="naver-site-verification" content="3c1362974d453b6fe1fdd5089325e29980b1369e" />

  {% include_cached favicon.html %}

  <link id="main-css" rel="stylesheet" href="{{ '/assets/css/just-the-docs-light.css' | relative_url }}">
  <script defer src="{{ '/assets/js/vendor/lunr.min.js' | relative_url }}"></script>
  <script defer src="{{ '/assets/js/vendor/lunr.stemmer.support.min.js' | relative_url }}"></script>
  <script defer src="{{ '/assets/js/vendor/lunr.multi.min.js' | relative_url }}"></script>
  <script defer src="{{ '/assets/js/vendor/lunr.ko.min.js' | relative_url }}"></script>
  <script defer src="{{ '/assets/js/fslightbox.js' | relative_url }}"></script>
  <script defer src="{{ '/assets/js/lazysizes.min.js' | relative_url }}"></script>
  <script defer src="{{ '/assets/js/just-the-docs.js' | relative_url }}"></script>

  {% seo %}
</head>

assets/js/just-the-docs.js

function tagManager() {
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());

  {% for ga_property in ga_tracking_ids %}
  gtag('config', '{{ ga_property }}'{% unless site.ga_tracking_anonymize_ip == nil %}, { 'anonymize_ip': true }{% endunless %});
  {% endfor %}
}

function loadScript(url) {
  return new Promise((resolve, reject) => {
    var script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = url;
    script.defer = true;
    script.onload = resolve;
    script.onerror = () => reject(`스크립트 로드 중에 오류가 발생했습니다: ${url}`);
    document.head.appendChild(script);
  });
}

jtd.onReady(function(){
  initNav();
  ...
  {%- if site.ga_tracking != nil %}
  {% assign ga_tracking_ids = site.ga_tracking | split: "," %}
	  loadScript('https://www.googletagmanager.com/gtag/js?id={{ ga_tracking_ids.first }}')
	  tagManager();
  {%- endif %}
  window.onload = setTimeout(() => {
    document.querySelectorAll(".skeleton_loading").forEach(element => {
      element.classList.toggle("fade")
    })
  }, 300)
});

변경 후 스크립트 실행이 지연된 것을 확인하였고, 성능 점수도 좋아진 것을 확인했습니다.

Reference

[HTML] script 태그의 위치와 defer, async 속성

https://www.inflearn.com/course/lecture?courseSlug=인프콘2023-다시보기&unitId=177896&tab=curriculum