웹 애플리케이션을 배포했는데 초기 로딩이 느리다는 피드백을 받아본 적 있나요? 대부분의 경우, 원인은 JavaScript 번들 사이즈에 있습니다. 번들이 커지면 사용자가 페이지를 보기까지 걸리는 시간이 길어지고, 이탈률은 올라가고, SEO 순위는 내려갑니다.
이 글에서는 프론트엔드 번들 사이즈를 효과적으로 줄이는 핵심 전략 네 가지를 다룹니다. Tree Shaking, 코드 스플리팅, 의존성 최적화, 그리고 번들 분석 도구 활용법까지 실전 코드 예제와 함께 정리했으니, 지금 바로 프로젝트에 적용해 보세요.
번들 사이즈는 왜 중요한가요?
JavaScript 번들 사이즈는 웹 성능의 핵심 지표입니다. 브라우저는 번들을 다운로드하고, 파싱하고, 실행해야 비로소 화면을 그릴 수 있기 때문입니다. 번들이 1MB를 넘어가면 3G 네트워크에서는 다운로드만 수 초가 걸립니다.
번들 사이즈가 커지면 구체적으로 어떤 문제가 발생할까요?
- 초기 로딩 속도 저하: 사용자가 첫 화면을 보기까지 걸리는 시간(LCP)이 길어집니다. LCP는 Core Web Vitals의 핵심 지표 중 하나이며, Lighthouse 점수를 높이는 방법에서도 자세히 다룬 바 있습니다.
- SEO 순위 하락: Google은 Core Web Vitals를 랭킹 시그널로 사용하며, 로딩 속도가 느린 페이지는 검색 결과에서 불리합니다.
- 이탈률 증가: 페이지 로딩이 1초 지연될 때마다 전환율이 약 7% 감소한다는 연구 결과가 있습니다.
- 모바일 사용자 경험 악화: 모바일 네트워크에서는 번들 크기의 영향이 데스크톱 대비 훨씬 큽니다.
따라서 번들 사이즈 최적화는 선택이 아니라 필수입니다. 그렇다면 어디서부터 시작해야 할까요? 먼저 현재 번들 상태를 정확히 파악하는 것이 첫 번째 단계입니다.
번들 분석 도구로 현황 파악하기
최적화의 시작점은 “지금 내 번들에 뭐가 들어 있는지”를 아는 것입니다. 번들 분석 도구는 프로젝트의 의존성을 트리맵 형태로 시각화해서, 어떤 라이브러리가 가장 큰 비중을 차지하는지 한눈에 보여줍니다.

Vite 프로젝트에서 사용하기 (rollup-plugin-visualizer):
bash
npm install --save-dev rollup-plugin-visualizer
javascript
// vite.config.js
import { visualizer } from 'rollup-plugin-visualizer';
export default defineConfig({
plugins: [
react(),
visualizer({
filename: 'stats.html',
open: true,
gzipSize: true,
brotliSize: true,
}),
],
});
Webpack 프로젝트에서 사용하기 (webpack-bundle-analyzer):
bash
npm install --save-dev webpack-bundle-analyzer
npx webpack-bundle-analyzer dist/stats.json
빌드 후 생성된 트리맵 리포트에서 다음 세 가지를 집중적으로 확인하세요.
- 전체 번들에서 가장 큰 비중을 차지하는 라이브러리
- 여러 청크에 중복 포함된 코드
- 실제로 사용하지 않지만 번들에 포함된 모듈
제 경우 한 프로젝트에서 번들 분석을 처음 돌렸을 때, Moment.js 하나가 전체 번들의 25%를 차지하고 있는 것을 발견했습니다. 이것만 교체해도 번들이 눈에 띄게 줄어들었죠. 분석 없이 감으로 최적화하는 것은 비효율적이므로, 반드시 도구부터 세팅하고 시작하시기 바랍니다.
npm에 새 패키지를 추가할 때는 Bundlephobia를 활용하면 설치 전에 해당 패키지의 번들 크기(minified + gzip)를 미리 확인할 수 있습니다. VS Code에서는 Import Cost 확장을 설치하면 에디터에서 import 문 옆에 실시간으로 크기가 표시되므로 함께 활용해 보세요.
Tree Shaking이란 무엇이고, 어떻게 동작하나요?
Tree Shaking은 사용하지 않는 코드를 빌드 단계에서 자동으로 제거하는 최적화 기법입니다. “나무를 흔들어 죽은 잎을 떨어뜨린다”라는 비유에서 이름이 유래했습니다.
Tree Shaking은 ES Module(import/export)의 정적 구조를 분석해 동작합니다. 번들러가 코드의 의존성 그래프를 추적하고, 실제로 참조되지 않는 export를 최종 번들에서 제거하는 원리입니다.

Tree Shaking이 제대로 동작하려면?
Tree Shaking의 효과를 극대화하려면 몇 가지 조건을 지켜야 합니다.
ES Module 문법 사용하기:
javascript
// Tree Shaking 가능 — ES Module named export
import { debounce } from 'lodash-es';
// Tree Shaking 불가 — CommonJS require
const _ = require('lodash');
const debounce = _.debounce;
위 예제에서 첫 번째 방식은 debounce만 번들에 포함되지만, 두 번째 방식은 Lodash 전체가 번들에 들어갑니다. CommonJS(require)는 정적 분석이 불가능하기 때문입니다. Tree Shaking의 원리와 세부 설정 방법은 Webpack 공식 문서의 Tree Shaking 가이드에서 더 깊이 있게 다루고 있습니다.
package.json에 sideEffects 설정하기:
json
{
"name": "my-library",
"sideEffects": false
}
sideEffects: false를 선언하면 번들러에게 “이 패키지의 모든 모듈은 사이드 이펙트가 없으니, 사용하지 않는 export는 안전하게 제거해도 됩니다”라고 알려주는 것입니다. CSS 파일처럼 import만으로도 효과가 있는 파일은 배열로 예외 지정할 수 있습니다.
json
{
"sideEffects": ["*.css", "*.scss"]
}
Vite(Rollup)와 Webpack의 Tree Shaking 차이:
Vite는 프로덕션 빌드에 Rollup을 사용하는데, Rollup은 처음부터 Tree Shaking을 핵심 기능으로 설계한 번들러입니다. 별도 설정 없이 자동으로 사용하지 않는 import를 제거합니다. Webpack도 프로덕션 모드(mode: 'production')에서 Tree Shaking을 지원하지만, 라이브러리에 따라 Rollup보다 제거 효율이 떨어지는 경우가 있습니다. 두 번들러의 성능 차이가 궁금하다면 Vite vs Webpack 2026, 속도 차이가 얼마나 날까? 글을 함께 읽어보시기 바랍니다.
실제로 같은 React 프로젝트를 두 번들러로 빌드해 비교하면, Vite(Rollup) 쪽이 더 작은 번들을 생성하는 경우가 많습니다. 2026년 현재 새 프로젝트를 시작한다면 Vite를 기본 선택으로 고려하는 것이 합리적입니다.
코드 스플리팅으로 초기 로딩을 어떻게 줄일 수 있나요?
Tree Shaking이 “안 쓰는 코드를 제거”하는 전략이라면, 코드 스플리팅은 “지금 당장 필요 없는 코드를 나중에 불러오는” 전략입니다. 전체 애플리케이션을 하나의 거대한 번들로 보내는 대신, 여러 개의 작은 청크(chunk)로 나누어 필요한 시점에 로드합니다.
라우트 기반 코드 스플리팅
가장 효과적이고 적용하기 쉬운 방식은 라우트(페이지) 단위로 코드를 분리하는 것입니다.
React에서 React.lazy와 Suspense 활용:
jsx
import { lazy, Suspense } from 'react';
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
);
}
이렇게 하면 사용자가 /dashboard에 접속할 때 Dashboard 컴포넌트의 코드만 로드되고, Settings 코드는 해당 페이지를 방문하기 전까지 다운로드되지 않습니다.
컴포넌트 레벨 코드 스플리팅
페이지 내에서도 무거운 컴포넌트는 동적 import로 분리할 수 있습니다. 예를 들어 리치 텍스트 에디터, 차트 라이브러리, 지도 컴포넌트처럼 용량이 큰 모듈은 실제로 필요한 순간에만 로드하는 것이 좋습니다.
Next.js에서 dynamic import 활용:
jsx
import dynamic from 'next/dynamic';
const RichTextEditor = dynamic(
() => import('@/components/RichTextEditor'),
{
loading: () => <p>에디터 로딩 중...</p>,
ssr: false,
}
);
ssr: false 옵션은 서버 사이드 렌더링을 건너뛰게 해서, 브라우저 API에 의존하는 컴포넌트의 에러를 방지합니다.
코드 스플리팅 적용 시 주의사항
코드 스플리팅을 과도하게 적용하면 오히려 역효과가 날 수 있습니다. 청크 수가 너무 많아지면 HTTP 요청 횟수가 증가하고, 각 청크를 로드할 때마다 네트워크 왕복이 발생하기 때문입니다. 일반적으로 라우트 단위 스플리팅을 기본으로 하고, 50KB 이상의 무거운 컴포넌트만 추가로 분리하는 것이 적절합니다.
무거운 라이브러리를 가벼운 대안으로 교체할 수 있을까?
번들 분석을 해보면, 전체 크기의 상당 부분을 서드파티 라이브러리가 차지하고 있는 경우가 많습니다. 특히 오래된 라이브러리는 최신 JavaScript 기능을 활용하지 못해 불필요하게 무거운 경우가 있습니다. 무거운 라이브러리를 경량 대안으로 교체하는 것만으로도 번들 사이즈를 크게 줄일 수 있습니다.
대표적인 교체 대상과 대안
| 기존 라이브러리 | 크기 (min+gzip) | 대안 라이브러리 | 크기 (min+gzip) | 절감 효과 |
|---|---|---|---|---|
| Moment.js | 약 72KB | Day.js | 약 2KB | 약 97% 감소 |
| Lodash (전체) | 약 70KB | es-toolkit | 약 2~5KB (사용 함수만) | 약 90% 이상 감소 |
| Lodash (전체) | 약 70KB | 네이티브 ES6+ | 0KB | 100% 감소 |

Moment.js를 Day.js로 교체하기:
Moment.js는 공식적으로 “유지보수 모드의 레거시 프로젝트”로 선언되었습니다. Day.js는 Moment.js와 거의 동일한 API를 제공하면서 크기는 약 2KB에 불과합니다.
javascript
// Moment.js (72KB)
import moment from 'moment';
moment().format('YYYY-MM-DD');
// Day.js (2KB) — API가 거의 동일
import dayjs from 'dayjs';
dayjs().format('YYYY-MM-DD');
Lodash를 ES6+ 네이티브 메서드로 대체하기:
ES6 이후 JavaScript에는 Array.map(), Array.filter(), Object.entries(), 스프레드 연산자(...) 등 Lodash의 주요 기능을 대체하는 네이티브 메서드가 다수 추가되었습니다. 네이티브 메서드를 활용하면 추가 의존성 없이 동일한 결과를 얻을 수 있습니다.
javascript
// Lodash 사용
import _ from 'lodash';
const result = _.uniq([1, 2, 2, 3]);
// ES6+ 네이티브 — 추가 의존성 없음
const result = [...new Set([1, 2, 2, 3])];
Lodash의 고급 유틸리티가 필요한 경우에는 es-toolkit을 고려해 보세요. es-toolkit은 TypeScript 기반으로 만들어진 최신 유틸리티 라이브러리로, Lodash 대비 함수당 최대 96%까지 작은 번들 크기를 제공합니다.
만약 Lodash를 당장 완전히 제거하기 어렵다면, 최소한 lodash 대신 lodash-es를 사용하고, 개별 함수만 named import하세요. 이것만으로도 Tree Shaking이 가능해져서 번들 크기가 큰 폭으로 줄어듭니다.
javascript
// 나쁜 예 — Lodash 전체 포함
import _ from 'lodash';
// 좋은 예 — 필요한 함수만 import
import { debounce } from 'lodash-es';
추가로 적용할 수 있는 최적화 전략은?
위 세 가지 핵심 전략 외에도, 번들 사이즈를 더 줄일 수 있는 실전 기법이 있습니다.
프로덕션 모드 빌드 확인
기본적이지만 놓치기 쉬운 부분입니다. 개발 모드로 빌드하면 소스맵, 디버깅 코드, 경고 메시지 등이 모두 포함되어 번들이 2~3배 이상 커집니다. 배포 전 반드시 프로덕션 모드로 빌드하세요.
bash
# Vite
vite build
# Webpack
webpack --mode production
압축 설정 (Gzip / Brotli)
서버에서 번들 파일을 압축하여 전송하면 실제 다운로드 크기를 대폭 줄일 수 있습니다. Brotli 압축은 Gzip 대비 약 15~25% 더 작은 파일을 만들어냅니다. Vercel, Netlify 같은 프론트엔드 배포 플랫폼은 기본적으로 Brotli/Gzip 압축을 지원하므로 별도 설정이 필요 없는 경우가 많습니다.
이미지 및 정적 에셋 최적화
JavaScript 번들만큼 중요한 것이 이미지 최적화입니다. WebP 또는 AVIF 포맷으로 변환하고, Lazy Loading을 적용하면 전체 페이지 로딩 시간을 크게 단축할 수 있습니다.
정기적인 의존성 감사
npm ls --all이나 npx depcheck을 주기적으로 실행해서 사용하지 않는 패키지를 제거하세요. 프로젝트가 커질수록 “설치만 해두고 실제로는 안 쓰는” 의존성이 쌓이게 됩니다. 단순히 package.json에서 미사용 패키지를 제거하는 것만으로도 번들 크기가 줄어드는 경우가 있습니다.
번들 사이즈 최적화, 핵심 정리
번들 사이즈 최적화는 한 번에 끝나는 작업이 아니라, 프로젝트가 성장하면서 지속적으로 관리해야 하는 과정입니다. 핵심 전략을 다시 정리하면 다음과 같습니다.
- 번들 분석 도구로 현황 파악 — webpack-bundle-analyzer 또는 rollup-plugin-visualizer로 최적화 대상을 먼저 식별합니다.
- Tree Shaking 극대화 — ES Module 문법을 사용하고,
sideEffects설정을 확인합니다. - 코드 스플리팅 적용 — 라우트 기반 분리를 기본으로, 무거운 컴포넌트는 동적 import로 분리합니다.
- 무거운 라이브러리 교체 — Moment.js는 Day.js로, Lodash는 ES6+ 네이티브나 es-toolkit으로 교체합니다.
가장 효과적인 접근 순서는 “분석 → 가장 큰 라이브러리 교체 → Tree Shaking 설정 확인 → 코드 스플리팅”입니다. 작은 것부터 시작하더라도 측정 가능한 변화를 만들어낼 수 있습니다. 지금 바로 번들 분석 도구부터 설치해 보세요.
자주 묻는 질문 (FAQ)
Q1. 번들 사이즈 최적화는 언제 시작해야 하나요?
프로젝트 초기부터 번들 분석 도구를 설정해두는 것이 이상적입니다. 하지만 이미 운영 중인 프로젝트라면, Lighthouse 점수가 떨어지거나 초기 로딩이 3초를 넘을 때가 최적화를 시작할 시점입니다. 번들 분석 도구를 CI/CD 파이프라인에 통합하면 코드 변경 시마다 번들 크기 변화를 자동으로 추적할 수 있습니다. BundleMon이나 compressed-size-action 같은 도구를 GitHub Actions에 연결하면, PR마다 번들 크기 변화를 리포트로 받아볼 수 있습니다.
Q2. Tree Shaking과 코드 스플리팅은 어떤 차이가 있나요?
Tree Shaking은 빌드 시점에 코드에서 사용하지 않는 export를 제거하여 번들의 “총량”을 줄이는 기법입니다. 코드 스플리팅은 전체 번들을 여러 청크로 분할하여 “초기 로딩에 필요한 양”을 줄이는 기법입니다. 두 기법은 상호 보완적이며, 함께 적용해야 최대 효과를 얻을 수 있습니다. Tree Shaking으로 전체 코드량을 줄이고, 코드 스플리팅으로 초기 로드에 필요한 코드만 먼저 전달하는 것이 최적의 조합입니다.
Q3. Vite와 Webpack 중 번들 최적화에 더 유리한 것은 무엇인가요?
2026년 기준 새 프로젝트라면 Vite를 추천합니다. Vite는 프로덕션 빌드에 Rollup을 사용하는데, Rollup은 Tree Shaking 효율이 높고 기본 설정만으로도 작고 깔끔한 번들을 생성합니다. 다만, 대규모 엔터프라이즈 환경이나 Module Federation이 필요한 마이크로 프론트엔드 프로젝트라면 Webpack의 풍부한 플러그인 생태계가 여전히 유리할 수 있습니다. 두 도구 모두 Tree Shaking과 코드 스플리팅을 지원하므로, 기존 Webpack 프로젝트를 당장 마이그레이션할 필요는 없습니다. 두 번들러의 상세 비교는 Vite vs Webpack 2026 글을 참고하세요.