Vite vs. Webpack 2026: 마이그레이션 실전 가이드 (성능 비교 포함)
개발 서버 켜는데 30초, 코드 한 줄 고쳤는데 HMR 반영까지 5초. 혹시 이거 내 얘기인가요?
걱정 마세요, 여러분만 그런 거 아닙니다. 그런데 솔직히 말하면, 2026년에 이러고 있으면 뭔가 놓치고 있는 거예요.
JavaScript 번들러 판도가 완전히 바뀌었거든요. 한 10년 가까이 절대 강자였던 Webpack이 이제는 Vite랑 자리를 나눠 앉게 됐어요. Vite는 "개발할 때 거의 즉시 반응한다"고 약속하고, 진짜로 그렇게 동작하죠. 근데 여기서 중요한 건요, "Vite가 빠르다"만 가지고 선택할 문제가 아니라는 거예요.
이 글에서는 두 도구가 구조적으로 어떻게 다른지 파헤치고, Vite가 왜 그렇게 빠른지, 그리고 Webpack이 아직 더 나은 상황은 언제인지 살펴볼게요. 물론 실제 마이그레이션 가이드까지 전부요.
왜 지금 이 비교가 중요할까?
본격적인 기술 얘기 전에, 2026년 기준으로 왜 이게 더 중요해졌는지 짚어볼게요.
프론트엔드 개발이 달라졌어요
생태계가 확 바뀌었습니다:
- ES 모듈이 대세가 됨: 모던 브라우저 전부 네이티브 ES 모듈 지원해요. 개발 환경에서 JS 제공하는 방식 자체가 달라졌죠
- TypeScript가 기본: 전문 JS 개발자 78% 이상이 TS 쓰고 있어요. 빌드 도구가
.ts,.tsx잘 처리해야 하는 시대 - DX가 경쟁력: 기업들도 개발자 생산성이 제품 속도에 직결된다는 걸 알게 됐어요
- 빌드 시간 = 고통: 프로젝트 커질수록 HMR 100ms와 10초 차이가 하루에 몇 시간씩 쌓여요
느린 빌드가 얼마나 비싼지 아세요?
한번 계산해볼까요. 개발자 50명인 중간 규모 React 앱 기준으로:
| 지표 | Webpack (콜드 스타트) | Vite (콜드 스타트) |
|---|---|---|
| 개발 서버 시작 | 45초 | 400ms |
| HMR | 3-5초 | 50-200ms |
| 프로덕션 빌드 | 2-3분 | 20-40초 |
개발자마다 하루에 서버 4번 켜고, 코드 100번 수정한다고 치면:
- Webpack: 45초 × 4 + 3초 × 100 = 8분/일 대기
- Vite: 0.4초 × 4 + 0.1초 × 100 = 12초/일 대기
1년(250일) 기준으로:
- Webpack: 8분 × 50명 × 250일 = 16,667시간 날림
- Vite: 0.2분 × 50명 × 250일 = 417시간 날림
16,250시간 절약이에요. 풀타임 개발자 8명이 1년 내내 멍때리는 시간이랑 같아요.
구조가 어떻게 다른지 알아야 해요
Webpack: "일단 다 묶어" 방식
Webpack은 일단 전부 번들부터 만드는 철학이에요. webpack serve 치면 이런 일이 벌어져요:
┌─────────────────────────────────────────────────────────────┐
│ Webpack Dev Server │
├─────────────────────────────────────────────────────────────┤
│ 1. 의존성 그래프 전체 파싱 │
│ 2. 파일 전부 변환 (Babel, TypeScript 등) │
│ 3. 전부 메모리에 번들링 │
│ 4. 번들된 JS를 브라우저에 전달 │
│ 5. 코드 바뀌면: 영향받는 청크 다시 빌드 + HMR │
└─────────────────────────────────────────────────────────────┘
│
▼
┌──────────────┐
│ 브라우저 │
│ ────────────│
│ <script src= │
│ "bundle.js"/>│
└──────────────┘
핵심은 이거예요: Webpack은 브라우저한테 뭘 주기 전에 앱 전체를 처리해야 해요. 그래서 코드베이스 커질수록 시작 시간이 비례해서 늘어나는 거죠.
Vite: "필요할 때 그때그때" 방식
Vite는 네이티브 ES 모듈을 활용해서 완전히 다른 방식으로 접근해요:
┌─────────────────────────────────────────────────────────────┐
│ Vite Dev Server │
├─────────────────────────────────────────────────────────────┤
│ 1. 의존성 사전 번들링 (esbuild, 일회성) │
│ 2. index.html 즉시 제공 │
│ 3. 브라우저 요청 시 온디맨드로 파일 변환 │
│ 4. 네이티브 ESM 사용 - 브라우저가 모듈 해석 처리 │
│ 5. 변경 시: 단일 모듈 무효화, 즉각적인 HMR │
└─────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ 브라우저 │
│ ────────────────────────────────── │
│ <script type="module" src="main.js"/>│
│ │
│ import { App } from './App.tsx' │
│ import { useState } from 'react' │
│ // 브라우저가 각 모듈을 가져옴 │
└──────────────────────────────────────┘
비결은 간단해요: Vite는 개발할 때 소스 코드를 번들링 안 해요. 대신:
- 의존성만 esbuild로 딱 한 번 사전 번들링 (Go로 짜여져서 JS 번들러보다 10-100배 빠름)
- 소스 코드는 네이티브 ES 모듈 그대로 브라우저가 달라고 할 때만 변환
- 실제로 열어보는 파일만 처리, 전체 코드베이스 다 안 건드림
esbuild가 왜 이렇게 빠른가
Vite 속도의 비밀 무기가 esbuild예요. 의존성 사전 번들링을 담당하는데, 벤치마크 보면 입이 벌어져요:
three.js 10개 사본 번들링 (총: ~500만 줄의 코드)
┌─────────────┬────────────────┬──────────────────┐
│ 번들러 │ 시간 │ 상대 속도 │
├─────────────┼────────────────┼──────────────────┤
│ esbuild │ 0.37초 │ 1x │
│ parcel 2 │ 36.68초 │ 99배 느림 │
│ rollup │ 38.11초 │ 103배 느림 │
│ webpack 5 │ 42.91초 │ 116배 느림 │
└─────────────┴────────────────┴──────────────────┘
esbuild가 이렇게 빠른 이유:
- Go로 작성: 동시성 처리 끝내주는 컴파일 언어
- 병렬 처리: 멀티코어 CPU 풀로 활용
- AST 최소화: 진짜 필요한 변환만 함
- 전부 메모리: 디스크 안 건드림
근데 Webpack이 아직 나은 경우도 있어요
다 Vite로 갈아타기 전에, 솔직히 Webpack이 더 나은 상황도 있어요.
1. 복잡한 커스텀 로더 요구사항
Webpack의 로더 생태계는 매우 성숙해요. 다음과 같은 커스텀 로더가 있다면:
- 특수한 이미지 처리 파이프라인
- 커스텀 파일 형식 변환
- 복잡한 CSS 추출 시나리오
- 레거시 코드 변환
Vite의 플러그인 생태계에 직접적인 대안이 없거나, 로더를 Rollup 플러그인으로 다시 작성해야 할 수도 있어요.
// Webpack - .xyz 파일용 커스텀 로더 module.exports = { module: { rules: [ { test: /\.xyz$/, use: [ { loader: 'custom-xyz-loader' }, { loader: 'xyz-preprocessor', options: { /* ... */ } } ] } ] } };
2. Module Federation (마이크로 프론트엔드)
Webpack의 Module Federation은 여전히 마이크로 프론트엔드 아키텍처를 위한 가장 성숙한 솔루션이에요:
// Webpack Module Federation new ModuleFederationPlugin({ name: 'app1', remotes: { app2: 'app2@http://localhost:3002/remoteEntry.js', }, shared: { react: { singleton: true }, 'react-dom': { singleton: true }, }, });
Vite에도 vite-plugin-federation 같은 플러그인이 있지만, Webpack의 구현이 대규모 프로덕션에서 더 많이 검증되었어요.
3. 비표준 JavaScript 환경
최신 브라우저 이외의 환경을 타겟팅하는 경우:
- 특정 요구사항이 있는 Node.js 번들
- 복잡한 메인/렌더러 프로세스 빌드가 있는 Electron 애플리케이션
- 특수한 번들링이 필요한 Web Workers
Webpack의 출력 설정 유연성이 더 작업하기 쉬운 경우가 많아요.
4. 기존 Webpack 전문성을 가진 대규모 팀
팀이 수년간의 Webpack 전문성과 복잡하지만 잘 작동하는 빌드 설정을 가지고 있다면, 마이그레이션 비용이 이점을 초과할 수 있어요. 특히 현재 빌드가 "충분히 빠른" 경우에는요.
완벽한 Vite 마이그레이션 가이드
마이그레이션할 준비가 되셨나요? Create React App(Webpack 기반) 프로젝트에서 Vite로 실제 마이그레이션을 함께 살펴봅시다.
마이그레이션 전 체크리스트
시작하기 전에 현재 설정을 점검하세요:
# 현재 의존성 확인 cat package.json | grep -E "(webpack|babel|loader|plugin)" # 모든 webpack 관련 설정 파일 나열 find . -name "webpack*" -o -name ".babelrc*" -o -name "babel.config*" # 커스텀 로더/플러그인 식별 grep -r "loader:" webpack.config.js
문서화할 항목:
- 커스텀 로더와 그 목적
- 환경 변수 사용 패턴
- 정적 자산 처리 요구사항
- 프록시 설정 필요성
- PostCSS/Tailwind 설정
1단계: Vite와 의존성 설치
# CRA 관련 패키지 제거 npm uninstall react-scripts # Vite와 관련 패키지 설치 npm install -D vite @vitejs/plugin-react # TypeScript 사용 시 npm install -D @types/node
2단계: Vite 설정 생성
프로젝트 루트에 vite.config.ts 생성:
import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import path from 'path' export default defineConfig({ plugins: [react()], resolve: { alias: { '@': path.resolve(__dirname, './src'), '@components': path.resolve(__dirname, './src/components'), '@utils': path.resolve(__dirname, './src/utils'), }, }, server: { port: 3000, open: true, // API 요청 프록시 (CRA에서 이 설정이 있었다면) proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, }, }, }, build: { outDir: 'build', sourcemap: true, rollupOptions: { output: { manualChunks: { vendor: ['react', 'react-dom'], // 필요에 따라 다른 청킹 추가 }, }, }, }, // 환경 변수 접두사 (CRA는 REACT_APP_ 사용) envPrefix: 'VITE_', })
3단계: index.html 이동 및 업데이트
Vite는 index.html이 프로젝트 루트에 있어야 해요, /public이 아니라:
mv public/index.html ./index.html
HTML 파일 업데이트:
<!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8" /> <link rel="icon" type="image/svg+xml" href="/favicon.ico" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>My App</title> </head> <body> <div id="root"></div> <!-- 핵심 변경: Vite는 ES 모듈 사용 --> <script type="module" src="/src/main.tsx"></script> </body> </html>
주요 차이점:
%PUBLIC_URL%은 더 이상 필요 없음 - 절대 경로 사용- script 태그에
type="module"추가 - script가 엔트리 파일을 직접 가리킴
4단계: 엔트리 포인트 이름 변경
CRA는 src/index.tsx를 사용하지만, Vite는 관례적으로 src/main.tsx를 사용해요:
mv src/index.tsx src/main.tsx # 또는 vite.config.ts에서 index.tsx 사용하도록 설정: # build: { rollupOptions: { input: 'src/index.tsx' } }
5단계: 환경 변수 업데이트
이게 가장 큰 함정 중 하나예요. CRA는 REACT_APP_ 접두사를 사용하고, Vite는 VITE_를 사용해요:
# 모든 환경 변수 사용 찾기 grep -r "process.env.REACT_APP_" src/
옵션 A: 모든 사용 업데이트 (권장):
// Before (CRA) const apiUrl = process.env.REACT_APP_API_URL; // After (Vite) const apiUrl = import.meta.env.VITE_API_URL;
옵션 B: 호환성 심 생성 (임시 솔루션):
// src/env.ts export const env = { API_URL: import.meta.env.VITE_API_URL, DEBUG: import.meta.env.VITE_DEBUG === 'true', // ... 모든 환경 변수 매핑 };
TypeScript의 경우, 타입 정의 추가:
// src/vite-env.d.ts /// <reference types="vite/client" /> interface ImportMetaEnv { readonly VITE_API_URL: string readonly VITE_DEBUG: string // 다른 환경 변수 추가 } interface ImportMeta { readonly env: ImportMetaEnv }
6단계: 정적 자산 처리
정적 자산 처리 이동:
// Before (CRA) - Webpack이 import 처리 import logo from './logo.png'; // After (Vite) - 같은 문법, 다른 처리 import logo from './logo.png'; // URL 문자열 반환 // SVG를 React 컴포넌트로 사용하려면 플러그인 설치: // npm install -D vite-plugin-svgr import { ReactComponent as Logo } from './logo.svg';
SVGR을 위해 vite.config.ts 업데이트:
import svgr from 'vite-plugin-svgr' export default defineConfig({ plugins: [ react(), svgr({ svgrOptions: { // SVGR 옵션 }, }), ], })
7단계: CSS 모듈과 전처리기 처리
Vite는 CSS 모듈을 기본 지원해요:
// 설정 없이 바로 작동 import styles from './Button.module.css'; // SCSS의 경우, 전처리기 설치: // npm install -D sass import styles from './Button.module.scss';
PostCSS/Tailwind의 경우, Vite가 postcss.config.js를 자동 감지해요:
// postcss.config.js (이전과 동일) module.exports = { plugins: { tailwindcss: {}, autoprefixer: {}, }, }
8단계: 패키지 스크립트 업데이트
{ "scripts": { "dev": "vite", "build": "tsc && vite build", "preview": "vite preview", "lint": "eslint src --ext ts,tsx" } }
9단계: 일반적인 마이그레이션 이슈 처리
이슈 1: require() 사용
Vite는 ES 모듈을 사용하므로 require()가 작동하지 않아요:
// Before (CommonJS) const config = require('./config.json'); // After (ES Modules) import config from './config.json'; // 동적 require의 경우 const module = await import(`./modules/${name}.ts`);
이슈 2: 전역 변수
// Before (CRA가 제공) if (process.env.NODE_ENV === 'development') { /* ... */ } // After (Vite 대안) if (import.meta.env.DEV) { /* ... */ } if (import.meta.env.PROD) { /* ... */ } if (import.meta.env.MODE === 'development') { /* ... */ }
이슈 3: Jest에서 Vitest로 마이그레이션
Jest를 사용 중이라면 Vitest로 마이그레이션 고려 (같은 API, 더 빠름):
npm install -D vitest @testing-library/react @testing-library/jest-dom jsdom
// vitest.config.ts import { defineConfig } from 'vitest/config' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], test: { globals: true, environment: 'jsdom', setupFiles: './src/test/setup.ts', }, })
// src/test/setup.ts import '@testing-library/jest-dom';
테스트는 최소한의 변경으로 작동해요:
// Jest와 Vitest 모두에서 동일하게 작동 import { render, screen } from '@testing-library/react'; import { Button } from './Button'; describe('Button', () => { it('renders correctly', () => { render(<Button>Click me</Button>); expect(screen.getByRole('button')).toHaveTextContent('Click me'); }); });
10단계: CI/CD 업데이트
CI 설정 업데이트:
# GitHub Actions 예시 - name: Build run: | npm ci npm run build # 빌드 출력은 이제 'build/'에 있음 (vite.config.ts에서 설정 가능) - name: Deploy uses: actions/upload-artifact@v4 with: name: build path: build/
Vite 고급 최적화 기법
마이그레이션을 완료했다면, Vite에서 더 많은 성능을 끌어내는 방법을 알아볼게요.
1. 의존성 사전 번들링 최적화
어떤 의존성이 사전 번들링되는지 제어:
export default defineConfig({ optimizeDeps: { include: [ 'react', 'react-dom', // Vite가 놓칠 수 있는 의존성 포함 'lodash-es', 'axios', ], exclude: [ // ESM으로 유지해야 하는 의존성 제외 '@vueuse/core', ], }, })
2. 청크 분할 전략
프로덕션 번들 최적화:
export default defineConfig({ build: { rollupOptions: { output: { manualChunks: (id) => { // 모든 node_modules를 vendor 청크에 if (id.includes('node_modules')) { // 큰 라이브러리 추가 분할 if (id.includes('lodash')) return 'vendor-lodash'; if (id.includes('moment')) return 'vendor-moment'; if (id.includes('chart.js')) return 'vendor-charts'; return 'vendor'; } // 코드 스플리팅을 위해 기능별 분할 if (id.includes('/features/dashboard/')) return 'feature-dashboard'; if (id.includes('/features/admin/')) return 'feature-admin'; }, }, }, }, })
3. Vite 빌드 분석 활용
번들 분석:
# rollup 플러그인 설치 npm install -D rollup-plugin-visualizer
import { visualizer } from 'rollup-plugin-visualizer'; export default defineConfig({ plugins: [ react(), visualizer({ template: 'treemap', // 또는 'sunburst', 'network' open: true, gzipSize: true, brotliSize: true, filename: 'bundle-analysis.html', }), ], })
4. 환경별 설정
import { defineConfig, loadEnv } from 'vite'; export default defineConfig(({ command, mode }) => { const env = loadEnv(mode, process.cwd(), ''); return { plugins: [react()], define: { __APP_VERSION__: JSON.stringify(process.env.npm_package_version), }, build: { sourcemap: mode === 'staging', minify: mode === 'production' ? 'terser' : false, }, server: { proxy: mode === 'development' ? { '/api': env.VITE_API_PROXY_TARGET, } : undefined, }, }; });
성능 비교: 실제 벤치마크
200개 컴포넌트 React 애플리케이션의 실제 마이그레이션에서 나온 구체적인 수치를 살펴볼게요:
개발 경험
| 지표 | Webpack 5 | Vite 5 | 개선 |
|---|---|---|---|
| 콜드 스타트 (개발) | 34.2초 | 0.8초 | 42배 빠름 |
| 웜 스타트 (캐시됨) | 12.1초 | 0.3초 | 40배 빠름 |
| HMR (컴포넌트 변경) | 2.8초 | 0.05초 | 56배 빠름 |
| HMR (CSS 변경) | 1.2초 | 0.02초 | 60배 빠름 |
| 메모리 사용량 (개발) | 1.8GB | 0.4GB | 4.5배 적음 |
프로덕션 빌드
| 지표 | Webpack 5 | Vite 5 (Rollup) | 개선 |
|---|---|---|---|
| 빌드 시간 | 142초 | 38초 | 3.7배 빠름 |
| 출력 크기 (gzip) | 412KB | 398KB | 3% 작음 |
| 트리 쉐이킹 | 좋음 | 우수함 | 더 나은 DCE |
번들 품질
| 지표 | Webpack 5 | Vite 5 |
|---|---|---|
| 코드 스플리팅 | 수동 | 자동 |
| 트리 쉐이킹 | 좋음 | 우수함 |
| ES 모듈 출력 | 선택적 | 기본값 |
| 레거시 브라우저 지원 | 포함됨 | 플러그인 통해 |
일반적인 이슈 트러블슈팅
이슈: 특정 패키지에서 "Pre-transform error"
일부 패키지는 ESM 호환이 안 돼요:
export default defineConfig({ optimizeDeps: { include: ['problematic-package'], }, build: { commonjsOptions: { include: [/problematic-package/, /node_modules/], }, }, })
이슈: CSS/LESS/SASS import 순서 문제
export default defineConfig({ css: { preprocessorOptions: { scss: { additionalData: `@import "@/styles/variables.scss";`, }, }, }, })
이슈: 동적 import가 작동하지 않음
// Before (실패할 수 있음) const Component = lazy(() => import(`./pages/${page}`)); // After (명시적 경로가 Vite의 정적 분석에 도움) const Component = lazy(() => { switch(page) { case 'home': return import('./pages/Home'); case 'about': return import('./pages/About'); default: return import('./pages/NotFound'); } });
이슈: Node.js 내장 폴리필
Vite는 기본적으로 Node.js 폴리필을 포함하지 않아요:
npm install -D vite-plugin-node-polyfills
import { nodePolyfills } from 'vite-plugin-node-polyfills'; export default defineConfig({ plugins: [ react(), nodePolyfills({ include: ['buffer', 'process'], }), ], })
미래: 2026년 이후 무엇이 올까?
빌드 도구 환경은 계속 진화하고 있어요. 주목해야 할 것들:
Rolldown: Rust 기반 Rollup
Vite 팀이 Rolldown을 개발 중이에요. Rollup의 Rust 포트죠. 예상 이점:
- 10-20배 빠른 프로덕션 빌드
- 완전한 Rollup 플러그인 호환성
- Vite의 기본 번들러가 될 예정
Turbopack: Vercel의 해답
Vercel의 Rust 기반 번들러 Turbopack이 성숙해지고 있어요:
- 네이티브 Next.js 통합
- Webpack API 호환성 레이어
- Next.js 환경에서 Vite 대안이 될 수 있음
Oxc: 산화 컴파일러
Rust 기반 JavaScript 툴체인:
- 파서 (SWC보다 30배 빠름)
- 린터 (ESLint보다 50-100배 빠름)
- 트랜스포머
- 미니파이어
결론: 어떻게 선택할까요?
이제 질문은 "Vite vs. Webpack" 이 아니에요. "언제 Vite로 갈아탈까?" 가 맞는 질문이죠.
Vite로 가세요:
- 개발 서버 시작에 5초 넘게 걸림
- HMR에 1초 이상 걸림
- 새 프로젝트 시작함
- 느린 빌드 때문에 팀 생산성이 떨어짐
- 프로덕션에서 모던 브라우저만 지원해도 됨
Webpack 쓰세요:
- 잘 돌아가는 커스텀 로더가 복잡하게 얽혀있음
- Module Federation 많이 씀
- 지금 빌드가 "충분히 빠름"
- 마이그레이션 비용 > 생산성 이득
- 특수한 출력 요구사항 있음
2026년 기준, 대부분 팀한테 새 프로젝트는 Vite가 정답이고, 기존 프로젝트도 DX 문제 있으면 마이그레이션 할 가치 있어요.
숫자가 말해줘요. 30초 시작을 300ms로 바꾸면 개발 방식 자체가 바뀝니다. 플로우에 필요한 즉각적인 피드백 루프가 생기고, 하루에 몇 시간씩 까먹던 게 사라져요.
DX가 곧 제품 속도인 시대에, 이건 있으면 좋은 게 아니라 경쟁력입니다.
빠른 참조: 마이그레이션 체크리스트
- 현재 Webpack 설정과 커스텀 로더 점검
- Vite와 @vitejs/plugin-react 설치
- 동등한 설정으로 vite.config.ts 생성
- index.html을 프로젝트 루트로 이동
- script 태그를
type="module"로 업데이트 - 엔트리 포인트 이름 변경 또는 Vite에서 설정
- 환경 변수 업데이트 (REACT_APP_ → VITE_)
- 정적 자산과 SVG import 처리
- 필요시 CSS/SCSS 설정 업데이트
- Jest에서 Vitest로 테스트 마이그레이션 (선택)
- CI/CD 스크립트 업데이트
- 프로덕션 빌드 실행 및 출력 확인
- 벤치마크: 전/후 지표 비교
관련 도구 둘러보기
Pockit의 무료 개발자 도구를 사용해 보세요