Published on

Next.js 번들 사이즈 & Lighthouse 성능 최적화 정리

Authors
  • avatar
    Name
    강용석
    Twitter

FE 최적화

최적화의 필요성을 느낌

스타트업의 특성상 빠르게 개발이 진행되야 하는 경우가 많다.

생존과 투자와 직결되는 부분이기때문에 속도와 구현에 중점을 맞추게 된다.

그 과정중에 발생한 여러가지 이슈가 있는데 시간을 들여야하고 당장 개선해야할 기능적 이슈가 아니었기 때문에 생각만 하고있고 무시하고 진행했던 부분들을 짚어 보려고 한다.

원인

웹팩의 트리쉐이킹 한계로 인해 불필요한 코드까지 페이지에 포함되어서 페이지의 로드 사이즈가 커짐

사용하지 않는 스크립트로인해 번들 사이즈가 커지고 이로인해 여러가지 이슈가 발생

1. 라이트하우스 성능점수

낮으면 최저 10점대 또는 그나마 별거 없는 페이지더라도 30점 그리고 40점대 페이지는 사실상 찾아볼 수가 없다. 꽤 단순한 정적 페이지를 생성 하더라도 점수가 낮게 나오는거보면 단순 페이지 문제라기 보다는 프로젝트 전반에 걸쳐서 발생하고 있는 이슈라고 생각되었다.

스크린샷 2023-02-11 오후 4.42.26.png

스크린샷 2023-02-11 오후 4.42.34.png

2. 번들 사이즈의 비정상적인 증가

각각 페이지의 First Load JS 의 사이즈가 비슷비슷하다. 심지어 모두 1MB 가 넘는다. 스프린트가 하나 추가될때마다 전체 개별 페이지의 번들 사이즈가 같이 증가하는 이상현상을 발견했다. 그리고 이를 체크하기위해 그냥 단순 정적 페이지를 생성하고 사내 기본 규칙에 따라 컴포넌트를 활용하여 단 몇줄의 코드만 작성했는데도 1MB가 넘는 사이즈의 빌드 결과가 나왔다.

이러한 이유 때문인지 어느순간부터 빌드시간도 늘어났다는걸 체감하고 있다.

몇개만 캡쳐해봤는데 그냥 봐도 이상하다

몇개만 캡쳐해봤는데 그냥 봐도 이상하다

3. 로컬서버가 힙스택이 넘쳐서 자꾸 죽는 현상이 발생

개발을 진행하다보면 중간에 갑자기 페이지가 실행이 안되는 경우가 발생하는데 확인해보면 힙스택에 넘쳐서 개발서버 실행이 죽어버리는 현상이 발생했다. 아직 정확한 파악은 못했는데 이때문인지 운영중인 AWS ElasticBeanstack 도 가끔 죽어 서버가 재실행되는 경우가 발생하며 사용자에게 503 에러를 잠시동안 노출하게되는 케이스가 접수된적도 있었다. 서버의 용량을 올리는 방법으로 임시 방편으로 해결해두긴 했지만 제거되어야할 객체가 제대로 제거되지 않아 계속 쌓여 발생하는 문제이기 때문에 땜질 밖에 되지 않았다.

스크린샷 2022-12-01 오후 2.56.53.png

4. 서비스가 느려짐

다른 여러 서비스들에 비해 그렇게 많고 복잡한 기능을 포함하고 있는 서비스는 아니라고 생각한다. 그럼에도 불구하고 간헐적으로 느리다고 느껴지는 상황이 꽤 발생한다. 페이지 전환 사용자의 인터렉션에 의한 응답 동작 및 전체적으로 버퍼링이 걸린것같은 느낌

5. SEO 의 중요성

C2C 플랫폼의 서비스 특성상 SEO 는 중요한 과제중 하나다.

최근 검색을 통해서 유입되는 사용자가 줄었고 특히 네이버에서 유입되는 사용자는 거의 없다시피 하다.

짧은 기간안에 여러 기능이 추가되면서 많은 페이지와 코드와 패키지들이 추가되었는데 이로인해 개별 페이지들의 성능이 더 나빠지면서 SEO에도 영향이 가지 않았을까 추측이되었다.

Core Web Vital - LCP, CLS, FID 측정결과 최적화 필요

이러한 이유로….

최적화는 더이상 무시하고 그냥 넘어갈 수 없는 과제가 되었고 현 시점에서 무조건 달려가는 시간이 아닌 약간의 정비 시간과 SEO 지표를 다시 올리는것에 목적을 두는 시간을 갖게 되면서 관심을 두게 되었다.

더불어 출산을 통해 일주일의 출산 휴가를 받으면서 다른거에 방해 받지 않고 (트러블슈팅, 배포, 스프린트 등 회사에서 업무를 진행되며 중간 중간 발생되는 집중을 방해하는 포인트들) 좀더 최적화 라는 포인트에 집중해서 짚어보고 점검해 볼 수 있는 시간을 가지면서 정리할 시간을 가질 수 있었다.

최적화 방법

  1. Dynamic Import 활용
  2. package.json 에 sideEffects 옵션 추가
  3. export/import 모듈 사용 방식 변경
  4. S3 이미지 CDN 활용 및 webp 확장자 활용

문제 해결 접근 방법

기본 페이지 생성

test.tsx 로 기본 페이지 파일을 생성하고 해당 페이지의 번들 사이즈를 확인한다.

스크린샷 2023-02-11 오후 6.52.59.png

스크린샷 2023-02-11 오후 6.52.29.png

생성하자마자 1MB 가 넘어가는걸 확인 할 수 있다.

해당 테스트 페이지를 통해서 확인할 수 있는 부분은 app.tsx 를 체크해 봐야 할것으로 보여진다.

app.tsx

XXprovider 컴포넌트 형태로 만들어서 전역에서 주입해서 사용하고 있는 것들이 있는데

이중 일부는 페이지 로드와 동시에 사용되는것도 있지만 지연로드 시켜도 되는 것들이 있는데 이를 우선 구분할 필요가 있다고 생각했습니다.

그리고 vscode 의 확장 프로그램으로 제공하는 bundle size 를 활용하여 import 하고 있는 모듈들의 사이즈중 큰 사이즈를 차지하고 있는 모듈들을 확인해 보려고 합니다.

스크린샷 2023-02-11 오후 7.12.46.png

스크린샷 2023-02-11 오후 7.12.01.png

단일 모듈로 불러오고 있는데 세자릿수 KB 형태로 import 되고 있고 이러한 모듈들에 대한 최적화의 필요를 생각했습니다.

해결해야할 리스트

  • organisms - import/export 방식 변경
  • utils (common) - import/export 방식 변경
  • logEvent - logEvent 함수만 별도 파일로 분리
  • provider (app.tsx) - 일부 컴포넌트에 한해서 동적 가져오기 적용
  • package.json - sideEffects 옵션 추가해서 사용

해당 리스트들은 전페이지 여러 컴포넌트에 걸쳐서 사용되어지고 있기 때문에 일부 단일 페이지 컴포넌트의 수정만 한다고 바로 효과를 보는것이 아닌 하나의 페이지에 사용중인 함수 모듈 컴포넌트 모두를 살펴봐야 비로소 그 효과를 확인 할 수 있다.

현재 페이지 생성만해도 1MB 가 넘던걸 300~400KB 사이로 줄일 수 있을거라 생각합니다.

300KB보다 더 줄이는건 좀더 마이크로한 작업이 필요한 영역인것 같아서 1차로는 저정도로 만족 해야할것같고

전체적으로는 기존보다 최소 50% 이상의 번들 사이즈를 개선할 수 있을것같습니다

라이트하우스 기준 성능 지표도 20점 기준 대비 최소 2배에서 3배까지는 개선이 가능할것같습니다.

참고

  • 웹팩의 트리쉐이킹의 한계에 관련된 이슈내용
GitHub
Tree-shaking fails for different imports from the same file across multiple routes · Issue #34559 · vercel/next.js
Verify canary release I verified that the issue exists in Next.js canary release Provide environment information > next info /bin/sh: pnpm: command not found Operating System: Platform: darwin Arch...

참고자료

Syncfusion
Optimize Next.js App Bundle and Improve Its Performance | Syncfusion Blogs
In this article, we will learn how to optimize the Next.js app by reducing the bundle size and increase the score in Google PageSpeed Insights.
so-so.dev
Tree Shaking과 Module System
들어가며 어플리케이션은 개발자가 직접 작성한 코드, 외부 라이브러리 등 다양한 코드조각들로 이루어져 있습니다. 어플리케이션이 복잡해질수록 번들 사이즈에 신경을 쓰게 되는데요, 이때 필요한 코드만 남기기 위한 작업, 흔히 Tree Shaking이라고 불리는 과정이 수반되어야 합니다. 이 글에서는 Tree Shaking에 대한 기본 개념과 Tree Shaking을 보장하기 위한 요소들에 대해 알아봅니다. Tree Shaking이란? 우리가 흔히 명명하는 Tree Shaking…
googlechrome.github.io
Lighthouse Scoring calculator
web.dev
Web Vitals | Articles | web.dev
Essential metrics for a healthy site
(번역) ‘Create React App 권장을 Vite로 대체’ PR 대한 Dan Abramov의 답변

진행상황

  • utils/common.ts 내에 있는 유틸 함수들을 모두 개별 파일로 분리
  • package.json 에 sideEffects 옵션 추가
  • 1MB 넘던 파일들이 일부 페이지 제외 50%~60% 번들 사이즈 감소
  • common.ts 최적화 없이 sideEffects 옵션만 추가했을 경우 30% 정도 감소됨
  • common.ts 최적화만 진행했을경우 번들사이즈 변동 없음
  • app.tsx 에서 나중에 로드되도 되는 컴포넌트 동적 가져오기 적용으로 50KB 감소
  • next-i18next 제거 여부에 대한 번들 사이즈영향도 미미함 - 유지
  • logEvent 분리하는것 영향도 미미함 - 유지

성능 지표 분석

  • 불필요한 스크립트의 사이즈를 줄이는데에는 성공적
  • First Load JS 1.03MB ⇒ 평균 500KB 최소 446KB로 약 50% 이상 번들 사이즈 감소
  • /products/몽클레어-니트패딩-34698279 페이지 기준

스크린샷 2023-02-14 오후 12.33.21.png

스크린샷 2023-02-14 오후 12.34.02.png

app.js 컨텐츠 다운로드 345.85밀리초

app.js 컨텐츠 다운로드 345.85밀리초

app.js 컨텐츠 다운로드 189.27밀리초

app.js 컨텐츠 다운로드 189.27밀리초

app.js 최적화전

app.js 최적화전

app.js 최적화 후

app.js 최적화 후

스크린샷 2023-02-14 오후 12.46.41.png

스크린샷 2023-02-14 오후 12.46.53.png

라이트하우스 성능 측정결과 (main 기준) 는 예상과 다르게 큰 차이가 없습니다.

스크린샷 2023-02-14 오후 12.48.47.png

스크린샷 2023-02-14 오후 12.48.55.png

첫 페이지가 렌더링 되기 시작하는 시점을 보면 최적화 이후가 확실히 빠르게 보여지는걸 확인 할 수 있습니다

수치나 여러가지 측면에서 개선된걸 확인은 했지만 원했던 만큼의 성능 점수는 나오지 않고 있습니다.

라이트하우스

현재 라이트하우스는 버전 8,9를 사용하고 있고 최신 버전인 10이 릴리즈 되었습니다

10 버전은 크롬 112에서 제공될 예정이라고 합니다

10 버전에서 달라진건 TTI 지표가 성능점수를 측정하는데 제거된다고 합니다 대신 CLS의 측정 비중을 높인다고 하였습니다.

여기서 주목해야할 부분은 측정 비중입니다

10버전 기준

FCP 10%

SI 10%

LCP 25%

TBT 30%

CLS 25%

즉 성능 최적화를 진행하더라도 여기서 비중이 높은것 위주로 최적화를 진행해야 가성비대비 큰 효과를 볼 수 있을거라 보여집니다.

10버전 기준으로 성능점수를 측정하면 아래과 같습니다 (개선작업 반영)

스크린샷 2023-02-14 오후 12.55.03.png

라이트하우스 성능측정 계산기를 활용해서 저희가 원하는 80점대 이상의 성능 점수를 만들려면

LCP 와 TBT 의 수치를 최소화 하는데 집중을 해야합니다.

export / import

Bad

스크린샷 2023-05-08 오후 6.41.26.png

// keys.js
const a = {}
const b = {}
const c = {}
const result = { a, b, c }
export default result

//====================//
import foo from 'keys'
foo.a.name

Good

스크린샷 2023-05-08 오후 6.41.21.png

// keys.js
export const a = {}
export const b = {}
export const c = {}

//=================//
import { a } from 'keys'

무엇이 다르지?????

Named exports 사용하기 import { add, sum } from './math'

Default export 사용하기 가져올때 이름을 변경하여 사용가능

위 두 방법은 사용할경우 필요한 모듈만 가져와서 사용할 수 있다.

첫번째 방식같은경우 하나의 모듈만 호출해도 해당 파일에 정의된 모든 파일을 가져옴 (개발자도구 커버리지에서 사용되지않는 스크립트 확인 가능)

컴포넌트 최적화 필요

ProductMowebAppContents ProductRelatedProductList

cdn.channel.io 호출 뒤로 미루기 또는 필요한 페이지에서만 호출

놉 컴포넌트최적화가 아닌 api 의 최적화가 필요 백단 서버의 조력이 필요

그렇다 하더라도 빈페이지만 생성해도 성능점수가 60점밖에 나오지 않는건 이상함

어쨋든 프론트에서 최소 30점에 대한 개선점을 찾긴 해야함