Skip to content

bbbjihan/jihan-optimize-practice.web

Repository files navigation

jihan-optimize-practice.web

본 레포지토리는 웹페이지 최적화 연습을 위합니다.

성능 최적화 보고서 요약

  • 🎛️ PageSpeed Insights에서의 평가지표를 기준으로 하여 만점을 목표로 최적화 작업을 수행함.
항목 초기 커밋1 커밋2 커밋3 커밋4 커밋5 커밋6 커밋7 커밋8 결과
FCP 7 7 6 7 10 10 10 10 10 10
LCP 0 5 8 10 20 23 23 24 23 25
TBT 10 9 8 6 7 5 24 30 30 30
CLS 25 6 25 25 25 25 25 25 25 25
SI 10 10 10 10 10 10 10 10 10 10
총점 52 37 57 58 71 73 91 99 99 100
  • 평가 지표
    • FCP: First Contentful Paint 10%
    • LCP: Largest Contentful Paint 25%
    • TBT: Total Blocking Time 30%
    • CLS: Cumulative Layout Shift 25%
    • SI: Speed Index 10%
  1. 초기 평가지표 확인
  2. 모던 이미지 포맷 적용(jpg → webp)
  3. 이미지 조건부 로딩 적용
  4. 써드파티 script 태그 관리
  5. 웹 폰트 로컬 임포트
  6. 이미지 에셋 리사이징, picture태그 추가 및 width, height 명시
  7. 퍼스트파티 javascript 최적화
  8. section 분리 (best-sellers와 all-products 분리)
  9. 캐시 정책 추가
  10. (+)성능 외 평가항목 관련 업데이트

As-is

alt text

alt text

To-be

alt text

alt text

성능 지표 기반 성능 최적화 과정

0. 초기 평가지표 확인

alt text

1. 모던 이미지 포맷 적용(jpg → webp)

alt text

2. 이미지 조건부 로딩 적용

alt text

  • img 태그의 srcset과 sizes 속성을 활용하여 디바이스 사이즈에 맞는 이미지만을 로드하도록 설정해주었음
  • 각 image 태그에 loading=”eager”, loading=”lazy” 속성을 추가하여, 페이지 초기 진입 시의 우선순위를 태그에 명시해주었음.

To-be: 반응형으로 렌더링되는 이미지를 초기에 전부 로드해옴

...
<img class="desktop" src="images/Hero_Desktop.webp">
<img class="mobile" src="images/Hero_Mobile.webp">
<img class="tablet" src="images/Hero_Tablet.webp">
...

alt text

As-is: 현재 브라우저 크기에 맞게 필요한 이미지 태그만 렌더링(소스를 로드)함

...
<img 
  src="images/Hero_Desktop.webp" 
  srcset="images/Hero_Desktop.webp 1440w, images/Hero_Tablet.webp 960w, images/Hero_Mobile.webp 576w" 
  sizes="(max-width: 576px) 100vw, (max-width: 960px) 100vw, 100vw" 
	alt="Hero image"
  class="hero-image"
  loading="eager"
>
...

alt text

3. 써드파티 script 태그 관리

alt text

  • 초기 렌더링에 영향을 주지 않는 써드파티 스크립트의 경우, defer 프로퍼티를 적용하여 낮은 우선순위로 로드되도록 하였다.
  • 또한 스크립트의 로드 순서에 따라 DOM 객체가 생성되지 않아 접근하지 못할 때가 발생하여, DOMContentLoaded event listener를 활용하여 스크립트 동작 시기를 뒤로 미루어 주었다.

As-is

<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
    new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
    j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
    'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
    })(window,document,'script','dataLayer','GTM-XXXXXXXX');
</script>
<!-- End Google Tag Manager -->

<!-- Cookie Consent by FreePrivacyPolicy.com https://www.FreePrivacyPolicy.com -->
<script type="text/javascript" src="//www.freeprivacypolicy.com/public/cookie-consent/4.1.0/cookie-consent.js" charset="UTF-8"></script>
<script type="text/javascript" charset="UTF-8">
    cookieconsent.run({"notice_banner_type":"simple","consent_type":"express","palette":"light","language":"en","page_load_consent_levels":["strictly-necessary"],"notice_banner_reject_button_hide":false,"preferences_center_close_button_hide":false,"page_refresh_confirmation_buttons":false,"website_name":"Performance Course"});
</script>

alt text

To-be

<!-- Google Tag Manager -->
<script defer>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
    new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
    j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
    'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
    })(window,document,'script','dataLayer','GTM-XXXXXXXX');
</script>
<!-- End Google Tag Manager -->

<!-- Cookie Consent by FreePrivacyPolicy.com https://www.FreePrivacyPolicy.com -->
<script defer type="text/javascript" src="//www.freeprivacypolicy.com/public/cookie-consent/4.1.0/cookie-consent.js" charset="UTF-8"></script>
<script defer type="text/javascript" charset="UTF-8">
	window.addEventListener("DOMContentLoaded",() => cookieconsent.run({"notice_banner_type":"simple","consent_type":"express","palette":"light","language":"en","page_load_consent_levels":["strictly-necessary"],"notice_banner_reject_button_hide":false,"preferences_center_close_button_hide":false,"page_refresh_confirmation_buttons":false,"website_name":"Performance Course"}))
</script>

alt text

4. 웹 폰트 로컬 임포트

alt text

  • 구글 웹 폰트로 사용하고 있는 Heebo를 다운로드 받아, woff로 변환한 뒤 css에서 직접 Import해주었다.
  • HTML 코드 내부의 웹 폰트 로드 구문을 제거하여, 구글 웹 폰트 서버에서 폰트를 다운로드 받는 패킷을 제거하였다.
@font-face {
  font-family: 'Heebo';
  src: url('/css/fonts/Heebo-300.woff') format('woff');
  font-weight: 300;
}

@font-face {
  font-family: 'Heebo';
  src: url('/css/fonts/Heebo-400.woff') format('woff');
  font-weight: 400;
}

@font-face {
  font-family: 'Heebo';
  src: url('/css/fonts/Heebo-600.woff') format('woff');
  font-weight: 600;
}

@font-face {
  font-family: 'Heebo';
  src: url('/css/fonts/Heebo-700.woff') format('woff');
  font-weight: 700;
}

To-be

alt text

As-is

alt text

5. 이미지 에셋 리사이징, picture태그 추가 및 width, height 명시

alt text

  • 이미지 태그 내에 width, height이 명시되어 있지 않은 경우를 없애고 css로 리사이즈를 해주면서, 초기 렌더링 크기를 명시해주었다.
  • 초기 렌더링 크기에 맞게 이미지 에셋들을 리사이징하고 새로 압축해주었다.

6. 퍼스트파티 javascript 최적화

alt text

  • product.js에 렌더링 병목을 위해 인위적으로 작성된 무거운 연산 부분을 수정하였다.
  • 렌더링이 완료된 이후의 스크롤을 통해 최초 1회 product load의 동작 이후에 호출되도록 수정하였다.
function heavyFunction() {
  // Simulate heavy operation. It could be a complex price calculation.
  for (let i = 0; i < 10000000; i++) {
    const temp = Math.sqrt(i) * Math.sqrt(i);
  }
}

let loaded = false;

window.onload = () => {
  window.onscroll = () => {
    if (loaded) return;

    const status = "idle";
    const $productSection = document.querySelector("#all-products");
    const position =
      $productSection.getBoundingClientRect().top -
      (window.scrollY + window.innerHeight);

    if (status == "idle" && position <= 0) {
      loaded = true;
      loadProducts().finally(heavyFunction);
    }
  };
};

7. section 분리 (best-sellers와 all-products 분리)

alt text

  • 퍼스트 파티 이미지에 대해서 width와 height을 img 태그에 명시하고, aspect ratio를 유지하려고 하였으나 퍼스트 파티 이미지가 사용되는 best sellers section과 써드 파티 이미지가 사용되는 all products section이 코드상 분리되어있지 않다는 것을 확인함.
  • HTML의 section class를 분리해주고, css에서 best-sellers 섹션에 대해 적용되던 내용을 all-products에도 적용되도록 수정한 뒤, img 태그에 대해서만 분리해주었음.

8. 캐시 정책 추가

alt text

// ./firebase.json
{
  "hosting": {
    "public": "/",
    "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
    "headers": [
      {
        "source": "**/*.@(js|css|jpg|jpeg|gif|png|webp|ttf|woff|woff2)",
        "headers": [
          {
            "key": "Cache-Control",
            "value": "public, max-age=31536000"
          }
        ]
      }
    ]
  }
}

alt text alt text

+ 성능 외 평가항목 관련 업데이트

alt text

alt text