처음으로 웹 최적화 도전해보기-!

okkkkkky·2024년 6월 17일
0

웹 최적화는 어떻게 할 수 있을까요 ?

google chrome의 lighthouse를 실행시키게 된다면, lighthouse는 아주 친절하게도 어떤 부분에서 병목현상이 생기는지, 어떻게 개선시킬 수 있는지 많은 것들을 알려줍니다. 그 제안된 방법들을 따라 저는 아래의 방법들을 추가하여 실제로 40점대의 lighthouse 점수를 70점으로 향상시킬 수 있었습니다 (아직 갈길이 머네요..)

1. layout shift

layout shift는 말그대로 페이지의 레이아웃이 이동하는 것을 의미합니다. 예기치 않은 레이아웃 변경은 텍스트를 읽는 동안 원래 자리를 잃거나 텍스트가 갑작스럽게 이동하면 잘못된 링크나 버튼을 클릭하는 등 다양한 방식으로 사용자 환경을 방해할 수 있습니다. 경우에 따라 심각한 손상이 발생할 수 있습니다. 이를 위해, 저는 skeleton ui를 추가하여, 미리 렌더링될 화면의 큰 틀을 보여주고자 했습니다. 이는 layout shift로 인한 잘못된 클릭을 방지할 뿐만 아니라 사용자들에게 컨텐츠가 로딩되고 있음을 보여주며 사이트 이탈을 방지합니다.
skeleton ui

react-loading-skeleton

프로젝트를 진행하면서 다소 촉박한 시간으로 인해 직접 구현하기는 어려웠고, react-loading-skeleton이라는 라이브러리를 사용하여 쉽게 구현할 수 있었습니다. 패키지의 용량도 light해서 그렇게 부담이 가는 라이브러리는 아니었기에 더더욱 선택할 이유가 충분했습니다.

2. excessive dom elements

한번에 렌더링되는 dom 요소들이 너무나 많다면 이에 따라 네트워크 효율성이나 부하에 치명적인 성능저하가 발생할 수 있고, 런타임이나 메모리 성능등 좋지 않은 부작용들이 많이 발생할 수 있습니다.
맨 처음 이 현상에 대해 인지하고 있지 못했는데, 웹에서는 일어나지 않았으나, 모바일에서 인덱스 페이지를 접속하니 대기시간이 너무 오래걸려 접속하지 못하는 버그가 발생하게 되었습니다.

그래서 lighthouse에 접속하여 해당 에러 메세지를 발견하여 원인을 찾아보았더니 Swiper.js의 loop 모드에서 main image가 한개인 경우 여러개가 보여야 하는 이슈가 발생하는 것을 방지하기 위해 기존 코드에 이미지 리스트를 80개 복제하는 코드가 있었던 것을 발견했습니다.

이를 해결하기 위해 변경된 index 페이지에서는 최대 화면에서 3개만 보여지기 때문에 1개의 이미지만 렌더링되는 경우 6배, 2개의 이미지는 3배, 3개 - 4개의 이미지는 2배로 설정하여 하위 dom 요소들이 많아지지 않도록 조절하였습니다.

이렇게 수정만 하여도, 기존에 Total Blocking Time이 1000ms 였는데, 이를 40ms로 크게 줄일 수 있었습니다.

3. next/image

next.js를 사용하고 있다면, lighthouse에서는 next generation 확장자를 사용하거나, next/image를 사용하는 것을 권장하는 문구를 많이 볼 수 있습니다. next/image는 next.js 기반의 프로젝트에서 사용하고 있는 이미지들의 최적화를 돕는 패키지로 이를 통해 이미지의 속도 조절, viewport에 따른 이미지의 사이즈 조절, 그리고 이미지가 로딩되면서 보여지는 placeholder의 이미지 설정 등 다양한 옵션을 설정할 수 있습니다. 하지만 저는 아래와 같이 간단한 설정만 추가하였습니다.
next/image를 사용한 코드

이를 통해 LCP 속도를 개선할 수 있는데요, LCP는 웹페이지의 기본 콘텐츠가 로드되는 속도를 나타냅니다. 구체적으로 LCP는 사용자가 페이지 로드를 시작한 시점부터 표시 영역 내에서 가장 큰 이미지 또는 텍스트 블록이 렌더링될 때까지의 시간을 측정합니다.

여기에서 제일 중요한 것은 바로 priority 속성을 추가한 것입니다. lcp의 경우, 로드되는 속도를 빠르게 해야 하기 때문에 lazy loading을 오히려 적용시키는 것이 아니라 제일 우선순위로 적용시켜야 하기 때문에 꼭 적용해야 하는 사항입니다.

next/image와 관련된 내용은 다음 글에서 자세히 다뤄볼 예정입니다.

webpack bundle analyzer

페이지에서 사용되는 패키지는 어떤 패키지인지, 가장 큰 용량을 차지하고 어떤 불필요한 패키지들이 있는지 한눈에 확인할 수 있는 툴이 바로 webpack bundle analyzer일 것입니다. 이를 통해 어떤 패키지들을 tree-shaking할 수 있고 어떤 패키지들을 삭제할 것인지 확인할 수 있습니다.
webpack-bundle-analyzer

저는 next.js를 사용하고 있었기 때문에 next.js용 bundle 분석툴인 @next/bundle-analyzer를 설치하여 아래와 같은 명령어를 정의한 후 실행해주어 위와 같은 html 파일을 확인할 수 있었습니다.

// next.config.js
module.exports = withPlugins(
  [
    [
      withBundleAnalyzer({
        enabled: process.env.ANALYZE === 'true',
        openAnalyzer: false,
      }),
    ],
    ...
  ].filter(Boolean),
  nextConfig
);

// package.json
"analyze": "ANALYZE=true next build",

이를 통해 다이어트(?)시켜야 하는 패키지는 date-fns, lottie였습니다.
date-fns의 경우, tree-shaking을 통해 아래와 같이 수정할 수 있었고, 이에 따라 사용되지 않는 패키지까지 전부 import할 필요가 없어져 효과적으로 서드파티 자바스크립트 다운을 줄일 수 있었습니다.

// 기존
import { add, endOfMonth, format } from 'date-fns';

// 변경
import add from 'date-fns/add';
import endOfMonth from 'date-fns/endOfMonth';
import format from 'date-fns/format';

기존에 적용되어있던 lottie 라이브러리도 기존의 .json파일이 아닌 .lottie 확장자를 가진 파일로 변경함과 동시에 dot-lottie 전용 라이브러리로 교체함으로써 파일 자체의 용량과 lottie 관련 라이브러리가 번들에서 차지하는 부분을 줄일 수 있었습니다.

이외에도 사용되지 않는 자바스크립트 파일이나 css파일을 확인할 때, chrome 개발자 툴에서 command + shift + p를 통해 coverage 섹션을 찾고 페이지를 reload 하는 경우, 사용되지 않는 항목들을 확인하여 lazy loading 시키거나 그 이외의 작업들을 시행할 수 있습니다. (Usage Visualization 컬럼에서 빨간색이 차지하는 것이 이 페이지에서 사용되지 않는 자바스크립트 혹은 CSS입니다)

다만, 자바스크립트 파일의 경우, 이벤트가 일어남에 따라 시행되는 파일들도 있기 때문에 관련된 chunk js 파일들은 내부적으로 추가 확인을 한 후, 삭제 혹은 lazy loading이 필요할 수 있는 것을 감안해야 합니다.

chrome coverage

참고
https://web.dev/articles/vitals?hl=ko
https://web.dev/articles/cls?hl=ko
https://developer.chrome.com/docs/lighthouse/performance/dom-size?hl=ko
https://web.dev/articles/optimize-lcp?hl=ko
https://nextjs.org/docs/pages/building-your-application/optimizing/images

0개의 댓글