이 글에서는 Next.js Bundle Analyzer가 무엇인지, 번들을 어떻게 분석하는지, 그리고 실제로 번들 사이즈를 줄이기 위한 최적화 전략과 CI/CD 파이프라인에 통합하는 방법에 대해 다룹니다.
웹 애플리케이션 개발할 때 컴포넌트별로 파일을 나눠서 작업합니다. Header.js, Footer.js, Button.js... 이런 식으로 수십, 수백 개의 파일이 생깁니다.
개발 시 파일 구조:
src/
├── components/
│ ├── Header.js (5KB)
│ ├── Footer.js (3KB)
│ └── Modal.js (8KB)
├── utils/
│ ├── api.js (10KB)
│ └── helpers.js (4KB)
└── pages/
└── index.js (2KB)
브라우저가 이 파일들을 각각 요청하면 HTTP 요청이 너무 많아집니다. HTTP/1.1에서는 도메인당 동시 연결 수가 6-8개로 제한되어 있어서 성능이 저하됩니다.
HTTP/2나 HTTP/3에서는 멀티플렉싱으로 이 문제가 많이 개선되긴 했지만, 여전히 번들링이 필요한 이유들이 있습니다:
번들링은 이런 여러 파일들을 하나 또는 몇 개의 파일로 합치는 과정입니다.
빌드 후:
.next/static/chunks/
├── main-abc123.js (32KB) - 모든 파일이 합쳐짐
└── framework-def456.js (45KB) - React, Next.js 코어
번들이 클수록 문제가 많습니다:
네트워크별 100KB 다운로드 시간:
따라서 번들 크기를 최적화하는 것이 중요합니다.
npm install --save-dev @next/bundle-analyzer
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
})
module.exports = withBundleAnalyzer({
reactStrictMode: true,
// 기존 Next.js 설정
})
# Mac/Linux
ANALYZE=true npm run build
# Windows
set ANALYZE=true && npm run build
# package.json에 스크립트 추가 (선택사항)
"scripts": {
"analyze": "ANALYZE=true next build"
}
실행하면 브라우저에 아래 같은 이미지가 있는 3개의 탭이 순차적으로 열립니다.
static/chunks/pages/_app-xxx.js → 모든 페이지 공통 번들
static/chunks/pages/index-xxx.js → 홈페이지 전용
static/chunks/pages/about-xxx.js → about 페이지 전용
static/chunks/framework-xxx.js → React, Next.js 코어
_app
청크 안에 있으면 → 모든 페이지에서 로드 (불필요한 경우 최적화 필요)예시)
_app-xxx.js
안에 있음 → 모든 페이지에서 로드됨 (문제)dashboard-xxx.js
안에 있음 → dashboard 페이지만 로드 (OK)Client Bundle (가장 중요)
.next/static/chunks/pages/*.js
Server Bundle
Edge Bundle
트리맵에서 주의해야 할 패턴들:
Moment.js는 모든 언어 파일을 기본으로 포함합니다. 실제 크기는 약 290KB.
라이브러리 또는 프레임워크에서 자동으로 사용하지 않는 로케일을 제거하여 최적화를 해주는 경우도 있습니다
import moment from 'moment' // 290KB (모든 로케일 포함)
한국 서비스인데 아랍어, 스와힐리어 파일까지 다 들어있습니다.
해결 방법 1: 필요한 로케일만 포함
// next.config.js
const webpack = require('webpack')
module.exports = {
webpack: (config) => {
config.plugins.push(
new webpack.ContextReplacementPlugin(
/moment[\/\\]locale$/,
/ko|en/ // 한국어, 영어만 포함
)
)
return config
}
}
해결 방법 2: Day.js로 교체
Day.js는 2KB밖에 안 됩니다:
import dayjs from 'dayjs' // 2KB
// API가 거의 비슷해서 교체가 쉬움
dayjs().format('YYYY-MM-DD') // moment와 동일
Lodash 전체는 약 71KB입니다.
// 문제: 전체 라이브러리 포함 (71KB)
import _ from 'lodash'
const result = _.debounce(fn, 300)
// 해결: 필요한 함수만 (2-3KB)
import debounce from 'lodash/debounce'
Ant Design이나 Material-UI 같은 UI 라이브러리 사용할 때 자주 발생합니다.
Ant Design v4 이하:
// 전체 CSS 포함 (60KB+)
import 'antd/dist/antd.css'
// 해결: babel-plugin-import 사용
{
"plugins": [
["import", {
"libraryName": "antd",
"style": "css" // 사용하는 컴포넌트 CSS만
}]
]
}
오래된 브라우저 지원하려다가 폴리필을 너무 많이 넣는 경우가 있습니다.
// 모든 폴리필 포함
import 'core-js' // 89KB
// 필요한 것만
import 'core-js/features/promise'
import 'core-js/features/array/flat'
필요한 시점에 코드를 로드하는 방법입니다. 가장 효과적인 최적화 방법 중 하나.
import dynamic from 'next/dynamic'
// 컴포넌트 동적 로드
const HeavyComponent = dynamic(
() => import('../components/HeavyComponent'),
{
loading: () => <p>Loading...</p>,
ssr: false // 서버 렌더링 비활성화 (선택)
}
)
// 라이브러리 동적 로드
async function loadChart() {
const { Chart } = await import('chart.js')
// Chart 사용
}
사용하지 않는 코드를 자동으로 제거하는 기능입니다. ES6 모듈을 사용해야 작동합니다.
조건:
"sideEffects": false
설정// Tree Shaking 가능
import { debounce } from 'lodash-es'
// Tree Shaking 불가능
const _ = require('lodash')
무거운 라이브러리를 가벼운 대안으로 바꾸는 것도 좋은 방법입니다.
예를 들어
moment.js (67KB minified) → day.js (2.7KB minified)
lodash (71KB) → radash (전체 306KB, 개별 함수 1-5KB, 훨씬 더 tree-shakble 함)
uuid (11KB) → nanoid (2KB)
등등 대안을 사용하는 방법이 있습니다
Next.js는 페이지별로 자동 코드 스플리팅을 합니다.
// 페이지별 자동 분리
pages/
index.js → chunks/pages/index.js
about.js → chunks/pages/about.js
// 공통 코드는 별도 청크로
chunks/
framework.js // React, Next.js
commons.js //, 공통 컴포넌트
splitChunks 설정으로 청크를 더 효율적으로 분리할 수 있습니다.
// next.config.js
module.exports = {
webpack: (config, { dev, isServer }) => {
if (!dev && !isServer) {
// 중복 모듈 제거
config.optimization.splitChunks = {
chunks: 'all',
cacheGroups: {
vendor: {
test: /node_modules/,
name: 'vendor',
priority: 10
}
}
}
}
return config
}
}
npm install --save-dev bundlesize
// package.json
{
"bundlesize": [
{
"path": ".next/static/chunks/pages/_app-*.js",
"maxSize": "150 KB"
},
{
"path": ".next/static/chunks/pages/index-*.js",
"maxSize": "100 KB"
}
],
"scripts": {
"test:size": "bundlesize"
}
}
name: Bundle Size Check
on: [pull_request]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- run: npm ci
- run: npm run build
- run: npm run test:size
Google Core Web Vitals 권장사항:
번들 최적화는 한 번에 끝나는 작업이 아닙니다. 지속적인 모니터링과 관리가 필요합니다.
중요한 건 측정하고, 분석하고, 개선하는 사이클을 만드는 것입니다. Bundle Analyzer로 현재 상태를 파악하고, Dynamic Import와 Tree Shaking으로 최적화하고, CI/CD로 자동 모니터링하면 됩니다.
Google은 First Load JS를 200KB 이하로 권장합니다. 현실적으로 쉽지 않지만, 목표로 삼고 계속 개선해나가면 됩니다.