Next.js Bundle Analyzer 사용하여 번들 분석하고 최적화 하기

Y·2025년 8월 11일
0
post-thumbnail

이 글에서는 Next.js Bundle Analyzer가 무엇인지, 번들을 어떻게 분석하는지, 그리고 실제로 번들 사이즈를 줄이기 위한 최적화 전략과 CI/CD 파이프라인에 통합하는 방법에 대해 다룹니다.

목차

  1. 번들링이란
  2. Bundle Analyzer 설치 및 설정
  3. 트리맵 분석 방법
  4. 자주 발생하는 번들 문제들
  5. 최적화 전략
  6. 번들 크기 모니터링
  7. 마무리

번들링이란

번들링의 개념

웹 애플리케이션 개발할 때 컴포넌트별로 파일을 나눠서 작업합니다. 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에서는 멀티플렉싱으로 이 문제가 많이 개선되긴 했지만, 여전히 번들링이 필요한 이유들이 있습니다:

  • 압축 효율성 (여러 파일을 합치면 압축률이 더 좋음)
  • 파싱 오버헤드 감소
  • 모듈 시스템 변환 (브라우저가 이해할 수 있는 형태로)
  • Tree Shaking 등 최적화 적용

번들링은 이런 여러 파일들을 하나 또는 몇 개의 파일로 합치는 과정입니다.

빌드 후:
.next/static/chunks/
  ├── main-abc123.js (32KB) - 모든 파일이 합쳐짐
  └── framework-def456.js (45KB) - React, Next.js 코어

번들 크기가 중요한 이유

번들이 클수록 문제가 많습니다:

  • 다운로드 시간 증가
  • JavaScript 파싱 시간 증가
  • 실행 시간 증가
  • 메모리 사용량 증가

네트워크별 100KB 다운로드 시간:

  • 3G: 약 1초
  • 4G: 약 0.3초
  • 5G/Wi-Fi: 약 0.1초

따라서 번들 크기를 최적화하는 것이 중요합니다.

Bundle Analyzer 설치 및 설정

설치

npm install --save-dev @next/bundle-analyzer

next.config.js 설정

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 코어

특정 라이브러리가 어디에 포함되어 있는지 확인

특정 라이브러리가 어디에 포함되어 있는지 확인

  1. 트리맵에서 해당 라이브러리 블록 찾기
  2. 그것을 감싸고 있는 상위 청크 확인
  3. _app 청크 안에 있으면 → 모든 페이지에서 로드 (불필요한 경우 최적화 필요)
  4. 특정 페이지 청크 안에 있으면 → 해당 페이지만 로드 (정상)

예시)

  • a.js가 일부 페이지에서만 사용되는데 _app-xxx.js 안에 있음 → 모든 페이지에서 로드됨 (문제)
  • chart.js가 dashboard-xxx.js 안에 있음 → dashboard 페이지만 로드 (OK)

크기 종류

  • Stat Size: 압축 전 원본 크기
  • Parsed Size: 압축 후 번들 크기 (주목해야 할 값)
  • Gzipped Size: Gzip 압축 시 크기

Next.js의 세 가지 번들

  1. Client Bundle (가장 중요)

    • 브라우저에서 실행되는 코드
    • 사용자가 직접 다운로드
    • .next/static/chunks/pages/*.js
  2. Server Bundle

    • Node.js 서버에서 실행
    • getServerSideProps, API Routes
    • 크기보다 Cold Start 시간이 중요
  3. Edge Bundle

    • Edge Runtime용 (Middleware 등)
    • 1MB 크기 제한

위험 신호 파악하기

트리맵에서 주의해야 할 패턴들:

  • 거대한 단일 블록: 특정 라이브러리가 200KB 이상 차지하는 경우
  • 중복 패키지: 같은 라이브러리의 다른 버전이 여러 개 포함된 경우
  • node_modules 비중: 전체의 70% 이상이면 외부 의존성이 너무 많다는 신호
  • 사용하지 않는 코드: 큰 모듈이 포함되어 있는데 실제로는 일부만 사용

자주 발생하는 번들 문제들

1. Moment.js와 로케일

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와 동일

2. Lodash 전체 Import

Lodash 전체는 약 71KB입니다.

// 문제: 전체 라이브러리 포함 (71KB)
import _ from 'lodash'
const result = _.debounce(fn, 300)

// 해결: 필요한 함수만 (2-3KB)
import debounce from 'lodash/debounce'

3. CSS 프레임워크 중복

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만
    }]
  ]
}

4. 폴리필 과다

오래된 브라우저 지원하려다가 폴리필을 너무 많이 넣는 경우가 있습니다.

// 모든 폴리필 포함
import 'core-js'  // 89KB

// 필요한 것만
import 'core-js/features/promise'
import 'core-js/features/array/flat'

최적화 전략

Dynamic Import

필요한 시점에 코드를 로드하는 방법입니다. 가장 효과적인 최적화 방법 중 하나.

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 사용
}

Tree Shaking

사용하지 않는 코드를 자동으로 제거하는 기능입니다. ES6 모듈을 사용해야 작동합니다.

조건:

  • ES6 모듈 사용 (import/export)
  • package.json에 "sideEffects": false 설정
  • Production 빌드
// 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)
등등 대안을 사용하는 방법이 있습니다

Code Splitting

Next.js는 페이지별로 자동 코드 스플리팅을 합니다.

// 페이지별 자동 분리
pages/
  index.js    → chunks/pages/index.js
  about.js    → chunks/pages/about.js
  
// 공통 코드는 별도 청크로
chunks/
  framework.js  // React, Next.js
  commons.js    //, 공통 컴포넌트

Webpack 최적화 설정

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
  }
}

번들 크기 모니터링

bundlesize 패키지

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"
  }
}

GitHub Actions 자동화

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 권장사항:

  • First Load JS: 200KB 이하
  • Time to Interactive: 3.8초 이하
  • First Contentful Paint: 1.8초 이하

마무리

번들 최적화는 한 번에 끝나는 작업이 아닙니다. 지속적인 모니터링과 관리가 필요합니다.

중요한 건 측정하고, 분석하고, 개선하는 사이클을 만드는 것입니다. Bundle Analyzer로 현재 상태를 파악하고, Dynamic Import와 Tree Shaking으로 최적화하고, CI/CD로 자동 모니터링하면 됩니다.

유용한 도구들

  • bundlephobia.com: npm 패키지 설치 전에 크기 확인할 때
  • Chrome DevTools Coverage: 실제로 사용되는 코드 비율 확인
  • webpack-bundle-analyzer: Webpack 번들 상세 분석 (Next.js는 @next/bundle-analyzer 쓰는게 편함)
  • source-map-explorer: 소스맵 기반으로 번들 분석

Google은 First Load JS를 200KB 이하로 권장합니다. 현실적으로 쉽지 않지만, 목표로 삼고 계속 개선해나가면 됩니다.

profile
타입스크립트를 기반으로 프론트엔드와 백엔드 개발을 하고 있습니다. 인프라와 DevOps 영역도 틈틈이 공부하고 있고, 기술적 구현과 함께 비즈니스 관점에서의 고민도 놓치지 않으려 합니다

0개의 댓글