performance2025-06-10·13 min·33/348

next build 번들 크기 최적화 완전 가이드

Next.js 빌드 시 번들 크기를 줄여 로딩 성능을 향상시키는 다양한 최적화 방법을 알아봅니다.

next build 번들 크기 최적화 완전 가이드

Introduction

번들 크기는 페이지 로딩 시간에 직접적인 영향을 줍니다. Next.js의 빌드 과정에서 번들 크기를 최적화하면 사용자 경험과 SEO 모두에 도움이 됩니다. 이 글에서는 번들 분석부터 실제 최적화 기법까지 다룹니다.

Environment

# 프로젝트 구조
my-app/
├── app/
│   ├── page.tsx
│   ├── layout.tsx
│   └── components/
│       ├── HeavyComponent.tsx
│       └── Chart.tsx
├── lib/
│   └── utils.ts
├── package.json
└── next.config.js

# 기술 스택
Next.js: 14.2.5
React: 18.3.1
Bundle Analyzer: 0.6.0

Problem

빌드 후 번들 크기가 너무 큽니다:

$ npm run build

Route (app)                              Size     First JS load
┌ ○ /                                    5.2 kB        89 kB
├ ○ /about                               2.1 kB        86 kB
├ λ /blog/[slug]                         3.4 kB        87 kB
├ ○ /dashboard                          12.3 kB       156 kB
└ ○ /products                           15.8 kB       162 kB
+ First load JS shared by all            84 kB

⚠️ Some chunks are larger than 250 kB after minification.

Analysis

번들 크기 증가 원인:

  1. 라이브러리 임포트: 무거운 라이브러리 (moment.js, lodash 등)
  2. 트리쉐이킹 불가: 전체 라이브러리 임포트
  3. 코드 스플리팅 미적용: 동적 임포트 미사용
  4. 중복 코드: 여러 페이지에서 같은 코드 임포트

Solution

1. 번들 분석기 설정

// next.config.js
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');

/** @type {import('next').NextConfig} */
const nextConfig = {
  webpack: (config, { isServer }) => {
    if (!isServer && process.env.ANALYZE === 'true') {
      config.plugins.push(
        new BundleAnalyzerPlugin({
          analyzerMode: 'static',
          reportFilename: '../bundle-analyzer-report.html',
          openAnalyzer: false,
        })
      );
    }
    return config;
  },
};

module.exports = nextConfig;
# 번들 분석기 실행
ANALYZE=true npm run build

2. 동적 임포트로 코드 스플리팅

// ❌ 정적 임포트: 모든 컴포넌트가 초기 번들에 포함
import HeavyChart from '@/components/HeavyChart';
import MarkdownEditor from '@/components/MarkdownEditor';

export default function Dashboard() {
  return (
    
); }
// ✅ 동적 임포트: 필요 시에만 로드
import dynamic from 'next/dynamic';

const HeavyChart = dynamic(() => import('@/components/HeavyChart'), {
  loading: () => 
차트 로딩 중...
, ssr: false, // 서버 사이드 렌더링 비활성화 }); const MarkdownEditor = dynamic(() => import('@/components/MarkdownEditor'), { loading: () =>
에디터 로딩 중...
, }); export default function Dashboard() { return (
); }

3. 트리쉐이킹 최적화

// ❌ 전체 라이브러리 임포트
import _ from 'lodash';
import moment from 'moment';

export const format = (date: Date) => moment(date).format('YYYY-MM-DD');
export const debounce = (fn: Function) => _.debounce(fn, 300);
// ✅ 개별 함수 임포트 (트리쉐이킹 지원)
import { format } from 'date-fns';
import { debounce } from 'lodash-es';

export const formatDate = (date: Date) => format(date, 'yyyy-MM-dd');
export { debounce };
// ✅ 더 경량화된 대안
import { format } from 'date-fns';
import { useCallback, useRef } from 'react';

// lodash 대신 커스텀 구현
export function useDebounce any>(
  callback: T,
  delay: number
): T {
  const timeoutRef = useRef();
  
  return useCallback(
    (...args: any[]) => {
      if (timeoutRef.current) {
        clearTimeout(timeoutRef.current);
      }
      
      timeoutRef.current = setTimeout(() => {
        callback(...args);
      }, delay);
    },
    [callback, delay]
  ) as T;
}

4. 무거운 컴포넌트 분리

// components/ChartWrapper.tsx
'use client';

import { useState, useEffect } from 'react';

interface ChartWrapperProps {
  data: any[];
  type: 'line' | 'bar' | 'pie';
}

export function ChartWrapper({ data, type }: ChartWrapperProps) {
  const [Chart, setChart] = useState(null);
  
  useEffect(() => {
    // 차트 라이브러리를 동적으로 로드
    import('recharts').then((mod) => {
      const { LineChart, BarChart, PieChart } = mod;
      
      switch (type) {
        case 'line':
          setChart(() => LineChart);
          break;
        case 'bar':
          setChart(() => BarChart);
          break;
        case 'pie':
          setChart(() => PieChart);
          break;
      }
    });
  }, [type]);
  
  if (!Chart) {
    return 
차트 로딩 중...
; } return ; }

5. 이미지 최적화

// ❌ 정적 이미지 임포트
import heroImage from '../public/images/hero.jpg';

// ✅ next/image 사용
import Image from 'next/image';

export function Hero() {
  return (
    Hero
  );
}

6. CSS 최적화

/* ❌ 전체 라이브러리 CSS 임포트 */
@import 'bootstrap/dist/css/bootstrap.min.css';

/* ✅ 필요한 스타일만 임포트 */
@import 'bootstrap/dist/css/bootstrap-grid.min.css';
// next.config.js에서 CSS 최적화
const nextConfig = {
  // CSS 모듈 활성화
  cssModules: true,
  
  // PostCSS 설정
  postcss: {
    plugins: {
      tailwindcss: {},
      autoprefixer: {},
    },
  },
};

7. 빌드 설정 최적화

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  // 압축 활성화
  compress: true,
  
  // 제거할 패키지
  experimental: {
    optimizePackageImports: ['lodash', 'date-fns', 'rxjs'],
  },
  
  // 이미지 최적화
  images: {
    formats: ['image/avif', 'image/webp'],
    minimumCacheTTL: 60 * 60 * 24 * 30, // 30일
  },
};

module.exports = nextConfig;

8. 빌드 성능 모니터링

# 빌드 시간 측정
time npm run build

# 번들 크기 비교
# before: 84 kB shared
# after: 52 kB shared (38% 감소)
// package.json에 빌드 스크립트 추가
{
  "scripts": {
    "build": "next build",
    "build:analyze": "ANALYZE=true next build",
    "build:profile": "NEXT_PROFILE=true next build"
  }
}

Lessons Learned

  1. 번들 분석 필수: 최적화 전에 번들 분석으로 문제점 파악
  2. 동적 임포트 활용: 무거운 컴포넌트는 동적 임포트 적용
  3. 라이브러리 선택: 경량 라이브러리 우선 (moment.js → date-fns)
  4. 지속적 모니터링: CI/CD에서 번들 크기 변화 추적

This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.