Next.js

[Next.js] 페이지 성능 개선하기 2 - 번들 사이즈 개선, Tree Shaking

철스커 2023. 8. 21. 19:19
반응형

 

 

 

지난 시간에 측정했던 성능 점수입니다. (모바일 기준)

지난 시간에 성능 개선을 시도했던 흔적 👇

 

 

 

[Next.js] 페이지 성능 개선하기 1 - Next/image, CSS 프리로드

최근 들어 성능 최적화에 대한 고민을 많이 해보고 싶어서, 성능 최적화를 해봐야 겠다는 생각이 들었습니다. 예전에 진행했던 프로젝트 중 하나가, Lighthouse 점수가 낮게 나와서 몇 가지 개선을

cheolsker.tistory.com

 

 

 

 

 

번들 사이즈를 개선할 필요가 있을 것 같습니다.

@next/bundle-analyzer를 설치하고, next.config.js에 적용하였습니다.

 

 

 

 

@next/bundle-analyzer 설치

// 터미널에서 설치
yarn add -D @next/bundle-analyzer

또는

pnpm add -D @next/bundle-analyzer
// next.config.js

const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true',  // 환경변수 ANALYZE가 true일 때 실행
  openAnalyzer: false,  // 브라우저에 자동으로 분석결과를 새 탭으로 Open하는 것을 방지
});


...


module.exports = withBundleAnalyzer(nextConfig);

 

 

 

 

 

ANALYZE=true next build

빌드가 된 후, 번들된 파일들을 분석할 것이므로 위와 같이 빌드 명령어를 입력합니다.

 

 

 

 

빌드가 끝나면, .next 폴더에 analyze 폴더가 생성이 되고 번들링을 분석한 HTML 파일이 생성됩니다.

 

client.html :  클라이언트에서 사용되는 번들링 분석

nodejs.html :  서버사이드에서 사용되는 번들링 분석

edge.html :  ???, 이 파일을 열어보면 빈 화면만 나오고, 문서(docs) 상으로도 설명이 없습니다..

 

 

 

 

 

 

 

client.html를 열어 번들 파일 분석

client.html

 

 

클라이언트 쪽에서 사용하는 번들링만 확인해보려고 하는데요.

이 번들링 파일 중에서 _app-***.js 파일의 크기가 유독 큽니다.

 

 

 

_app-***.js 사각형 화면에 마우스를 올려보면, Stat size / Parsed size / Gzipped size 가 보입니다.

 

Stat size :  Minification, Uglify 등의 변형이 일어나기 전의 크기

Parsed size :  Minification, Uglify 등의 변형이 일어난 후의 크기 (최적화 후),  브라우저에서 파싱 후, 실행될 크기

Gzipped size : Gzip 알고리즘으로 압축된 파일 크기,  네트워크를 통해 전송되는 크기

 

(출처 : https://www.npmjs.com/package/webpack-bundle-analyzer#size-definitions)

 

 

 

즉, Parsed size와 Gzipped size를 줄인다면,

브라우저에서 자바스크립트를 다운로드하고 실행하는 시간네트워크 상에서 전송되는 시간을 단축시킬 수 있습니다.

 

 

 

 

 

 

chunk 번들
페이지별 번들

 

 

Next 빌드를 실행하면 위 그림과 같이 chunk 번들페이지 번들이 생성됩니다.

chunk 파일은 페이지 공통적으로 사용되는 것으로 알려져 있습니다.

 

bundle-analyzer로 이 chunk 번들을 살펴보면, 다음과 같이 추정해볼 수 있습니다.

 

chunks/framework-***  :  프레임워크와 관련 (react-dom과 react)

chunks/main-*** :  Next와 관련된 기능 (next/dist - router 포함)

chunks/pages/_app-***  :  _app 페이지와 관련

chunks/webpack-*** :   웹팩 스크립트 코드 (build-manifest.json을 확인해서 페이지 이동 시, 필요한 JS를 로드, 참고)

 

 

 

 

이 chunk들은 모든 페이지에서 사용되는 파일들이기 때문에, 파일 사이즈를 줄이는 것이 좋겠습니다.

다른 chunk과 다르게 _app-*** 파일이 크기도 하고 수정가능할 것으로 보입니다.

 

 

 

 

 

 

 

chunks/pages/_app-*** 번들 파일 분석

_app-*** 번들
_app-*** 번들에서 components 부분을 확대

 

 

_app 페이지는 공통 레이아웃, CSS, 로직들을 포함하고 있습니다.

그런데 Alert, Loading 등...  특정 동작이 발생할 때만, 컴포넌트를 렌더링해도 되는 경우도 있어서, 관련 컴포넌트는 지연 로딩으로 처리해도 될 것 같습니다. (초기 로딩에는 필요없기에)

 

 

 

또한 swiper, xregexp 등의 라이브러리도 사용해서 그런지 _app-*** 번들에 관련 모듈이 확인되고 있는데요.

_app 페이지의 코드를 살펴보면, 실제로 사용되고 있지는 않았습니다.

 

 

 

유틸 모듈에서 일부 함수만 export해서 사용하고 있는데, 유틸 모듈 내부의 export하지 않은 함수 일부가 xregexp 라이브러리를 사용하고 있습니다. 그런데 이 함수들도 빌드 과정에서 같이 번들링된 것입니다. 특정 모듈에서 사용하지 않은 함수들은 번들링될 필요가 없는데, 빌드 단계에서 이 함수들을 제거하지 않는 것으로 보입니다. 즉, Tree Shaking이 동작하고 있지 않았습니다.

 

 

 

 

 

컴포넌트 지연 로딩

import dynamic from 'next/dynamic';

...

const Spinner = dynamic(() => import('@/components/common/Spinner'));
const Portal = dynamic(() => import('@/components/layouts/Portal'));
const Alert = dynamic(() => import('@/components/common/Alert'));

function AppLayout({ children }: AppLayoutProps) {
    const { alert, closeAlert } = useAlert();
    const { isLoading } = useLoading();

    return (
        <StyledAppLayout>
            {children}
            {isLoading && <Spinner isFullScreen />}
            {alert.isOpen && (
                <Portal>
                    <Alert {...alert} onClose={closeAlert} />
                </Portal>
            )}
        </StyledAppLayout>
    );
}

위와 같이 dynamic을 사용하여 컴포넌트를 지연로딩 시켜줍니다.

Alert가 open되거나, 로딩이 true인 상태일 때 필요한 컴포넌트를 로딩 & 렌더링시켜주면 됩니다.

 

 

 

 

 

 

 

Tree Shaking 적용하기

package.json에 sideEffects 옵션을 제공하여 웹팩에게 사이드 이펙트(부수 효과)가 있는 파일들을 알려줄 수 있습니다.

 

 

// package.json

{
    "name": "opt",
    "version": "1.0.0",
    "sideEffects": false,
    ...
}

sideEffects를 false로 설정하면, 사이드 이펙트가 없기 때문에 웹팩에게 사용하지 않은 export는 빌드 단계에서 제거하도록 알려줍니다.

 

 

 

 

// package.json

{
    "name": "opt",
    "version": "1.0.0",
    "sideEffects": [
    	'**/*.css'
    ]
    ...
}

sideEffects가 있는 파일이 있으면 배열 안에 Glob 패턴의 문자열 값을 넣어줍니다.

이 파일들은 Tree Shaking 대상에서 제거됩니다.

 

 

 

 

 

 

_app-*** 번들
_app-*** 번들의 components 부분 확대

 

 

 

Tree Shaking을 적용한 후, 빌드된 _app 번들 파일을 확인해봤습니다.

그 결과, swiper, xregexp 라이브러리들이 _app-*** 번들에서 제거되었고, 지연로딩처리했던 Alert, Loading 등의 컴포넌트들도 번들에서 제거되었습니다.

 

 

번들 사이즈는 Parsed sizeGzipped size 각각 

550.11KB -> 292.07KB

169.76KB -> 88.32KB

로 줄어들었는데 기존의 절반에 가까운 사이즈로 줄어들었습니다.

 

 

줄어든 _app 번들 사이즈는 초기 페이지 로딩 개선에 도움이 될 것입니다.

 

 

 

 

 

 

모바일 기준, 성능 점수 결과

 

 

모바일 기준으로 59 -> 61점으로 소폭 상승했네요.

 

FCP 0.8s -> 0.8s  (동일)

LCP 7.4s  -> 6.2s  (-1.2s 만큼 개선)

TBT 100ms -> 40ms  (-60ms 만큼 개선)

CLS 0.247 -> 0.247  (동일)

Speed Index 5.4s -> 5.2s  (-0.2s 만큼 개선)

 

 

 

 

 

배너 이미지는 API 결과로 받아온 이미지 링크를 렌더링하기 때문에 늦어지는 부분이 있는데요.

이 부분을 개선하고, CLS 부분을 좀 더 개선할 필요가 있겠습니다.

 

 

 

 

 

 

참고

how-analyze-next-js-app-bundles

Tree Shaking | 웹팩

반응형