지난 시간에 측정했던 성능 점수입니다. (모바일 기준)
지난 시간에 성능 개선을 시도했던 흔적 👇
번들 사이즈를 개선할 필요가 있을 것 같습니다.
@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를 열어 번들 파일 분석
클라이언트 쪽에서 사용하는 번들링만 확인해보려고 하는데요.
이 번들링 파일 중에서 _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를 줄인다면,
브라우저에서 자바스크립트를 다운로드하고 실행하는 시간과 네트워크 상에서 전송되는 시간을 단축시킬 수 있습니다.
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 페이지는 공통 레이아웃, 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 대상에서 제거됩니다.
Tree Shaking을 적용한 후, 빌드된 _app 번들 파일을 확인해봤습니다.
그 결과, swiper, xregexp 라이브러리들이 _app-***
번들에서 제거되었고, 지연로딩처리했던 Alert, Loading 등의 컴포넌트들도 번들에서 제거되었습니다.
번들 사이즈는 Parsed size와 Gzipped 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 부분을 좀 더 개선할 필요가 있겠습니다.
참고
'Next.js' 카테고리의 다른 글
[Next.js] 페이지 성능 개선하기 3 - API Prefetch 하기 (0) | 2023.08.22 |
---|---|
[Next.js] 페이지 성능 개선하기 1 - Next/image, CSS 프리로드 (0) | 2023.08.17 |
[Next.js] Custom App 페이지를 이용해서 페이지 로그(log) 기록하기 (0) | 2023.03.18 |
[Next.js] Dynamic Import와 Suspense를 같이 쓰지 말기 (0) | 2023.03.09 |
[Next.js] Recoil, Expectation Violation: Duplicate atom key 이슈 (0) | 2022.11.13 |