Vite and Webpack are both JavaScript build tools — but they take fundamentally different approaches. Webpack bundles everything upfront and has dominated the ecosystem for a decade. Vite skips bundling during development entirely, using native ES modules in the browser, and only bundles for production. The result: Vite dev servers start in milliseconds while equivalent Webpack projects take seconds or minutes. This guide explains how each works, when each wins, and how to choose.
At a glance
| Vite | Webpack | |
|---|---|---|
| Released | 2020 (Evan You / Vue team) | 2012 (Tobias Koppers) |
| Dev server model | No-bundle (native ESM + esbuild) | Full bundle on start |
| Dev startup | < 1 second (most projects) | 10–60 s (large projects) |
| HMR speed | Instant (module-level) | Seconds (re-bundle affected) |
| Production bundler | Rollup | Webpack itself |
| Config complexity | Low (sensible defaults) | High (very explicit) |
| Ecosystem | Growing fast | Massive (10+ years) |
| TypeScript support | Built-in (esbuild transpile) | Via ts-loader or babel-loader |
| Legacy browser support | Via @vitejs/plugin-legacy |
Excellent (long-time focus) |
| Migration effort | Medium (from Webpack) | — |
What is Webpack?
Webpack (released 2012) is a static module bundler. It reads your entire dependency graph from an entry point, processes each file through loaders, applies plugins, and emits one or more bundles.
How Webpack works
Entry (index.js)
└─ imports a.js, b.css, logo.svg
└─ imports c.js
└─ ...
Webpack builds a full dependency graph, transforms every file,
and writes dist/main.js + dist/main.css (and more).
Key Webpack concepts:
| Concept | What it does |
|---|---|
| Entry | Starting point(s) for the dependency graph |
| Output | Where and how to write bundles |
| Loaders | Transform non-JS files (CSS, images, TypeScript) |
| Plugins | Broader tasks: HTML generation, env injection, tree-shaking |
| Mode | development vs production (sets defaults) |
| Code splitting | import() / SplitChunksPlugin for lazy loading |
Minimal webpack.config.js
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.tsx',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js',
clean: true,
},
resolve: {
extensions: ['.ts', '.tsx', '.js'],
},
module: {
rules: [
{
test: /\.(ts|tsx)$/,
use: 'ts-loader',
exclude: /node_modules/,
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.(png|svg|jpg|webp)$/,
type: 'asset/resource',
},
],
},
plugins: [
new HtmlWebpackPlugin({ template: './public/index.html' }),
],
mode: 'production',
};
Even this minimal config requires 3 npm packages just for TypeScript and CSS. Webpack gives you full control — but every feature must be explicitly configured.
What is Vite?
Vite (French for "fast", released 2020) takes a different approach for development vs production:
- Development: Serves source files directly as native ES modules (no bundling). The browser requests individual modules; Vite transforms them on-demand with esbuild (written in Go, ~100× faster than JS-based bundlers).
- Production: Bundles with Rollup for optimised output (tree-shaking, code splitting, minification).
How Vite works in dev mode
Browser requests /src/main.tsx
↓
Vite transforms on-demand (esbuild)
↓
Browser receives ES module, requests its imports
↓
Only touched modules are compiled — everything else: untouched
This means cold start doesn't depend on project size. A 10-file project and a 1000-file project start in roughly the same time.
Minimal vite.config.ts
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
// TypeScript, CSS modules, JSON, assets: all work out-of-the-box
});
TypeScript, JSX/TSX, CSS, CSS Modules, JSON imports, static assets — all supported with zero config. React, Vue, Svelte, Solid, Qwik each have an official plugin that adds the few framework-specific transforms.
Architecture deep-dive
Dev server comparison
| Webpack Dev Server | Vite Dev Server | |
|---|---|---|
| Startup | Bundles entire app first | Starts immediately, transforms on request |
| Startup time (small app) | 2–5 s | < 300 ms |
| Startup time (large app) | 30–120 s | < 1 s |
| HMR | Re-bundles affected modules | Sends only changed module via WS |
| HMR speed | 1–10 s | < 50 ms |
| Memory usage | High (holds full bundle in memory) | Low (transforms only requested modules) |
| Source maps | Available | Available |
| HTTPS | Via config | vite --https or config |
| Proxy | devServer.proxy |
server.proxy |
Production build comparison
Both produce optimised bundles, but with different underlying engines:
| Vite (Rollup) | Webpack | |
|---|---|---|
| Tree-shaking | Excellent (Rollup was built for this) | Good (requires sideEffects: false) |
| Code splitting | Automatic (dynamic import()) |
Explicit config or SplitChunksPlugin |
| Chunk naming | Automatic hash-based | Configurable |
| Output size | Often smaller (aggressive tree-shaking) | Comparable (tunable) |
| Build time | Fast (esbuild minifier) | Slower (JS-based minifier by default) |
| Asset handling | Built-in (inline < 4 kb, hash > 4 kb) | asset/resource rules |
| CSS | Extracted automatically | Needs mini-css-extract-plugin |
Configuration comparison
Aliases
// vite.config.ts
import { defineConfig } from 'vite';
import path from 'path';
export default defineConfig({
resolve: {
alias: { '@': path.resolve(__dirname, './src') },
},
});
// webpack.config.js
module.exports = {
resolve: {
alias: { '@': path.resolve(__dirname, 'src') },
},
};
Environment variables
// Vite — import.meta.env (only VITE_ prefix exposed to client)
const apiUrl = import.meta.env.VITE_API_URL;
// Webpack — process.env (via DefinePlugin or dotenv-webpack)
const apiUrl = process.env.REACT_APP_API_URL;
Vite exposes only variables prefixed with VITE_ to the browser, preventing accidental secret leakage. Webpack's process.env injection requires explicit configuration.
Proxy (API during development)
// vite.config.ts
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:3001',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},
});
// webpack.config.js
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3001',
changeOrigin: true,
pathRewrite: { '^/api': '' },
},
},
},
};
CSS Modules
// Both tools — same import syntax
import styles from './Button.module.css';
export function Button() {
return <button className={styles.primary}>Click</button>;
}
Vite supports CSS Modules out of the box. Webpack requires css-loader with modules: true.
Plugin ecosystems
| Need | Vite plugin | Webpack equivalent |
|---|---|---|
| React | @vitejs/plugin-react |
babel-loader + @babel/preset-react |
| Vue | @vitejs/plugin-vue |
vue-loader |
| Svelte | @sveltejs/vite-plugin-svelte |
svelte-loader |
| TypeScript | Built-in (esbuild) | ts-loader or babel-loader |
| Legacy browsers | @vitejs/plugin-legacy |
Built-in (targets config) |
| PWA | vite-plugin-pwa |
webpack-pwa-manifest |
| Bundle analysis | rollup-plugin-visualizer |
webpack-bundle-analyzer |
| SVG as component | vite-plugin-svgr |
@svgr/webpack |
| Image optimisation | vite-plugin-imagemin |
image-minimizer-webpack-plugin |
| Mock service worker | Works natively | Works natively |
Webpack's ecosystem is larger (10+ years), but Vite's plugin API is simpler and many Rollup plugins work in Vite directly. Most common needs are covered.
Speed benchmarks
Real-world numbers vary by project, but the pattern is consistent:
| Scenario | Webpack | Vite |
|---|---|---|
| Cold dev start (50 modules) | ~3 s | ~200 ms |
| Cold dev start (500 modules) | ~15 s | ~400 ms |
| Cold dev start (2000 modules) | ~60 s | ~800 ms |
| HMR (edit one component) | 1–3 s | 20–50 ms |
| Production build (500 modules) | ~25 s | ~8 s |
| Production build (2000 modules) | ~90 s | ~25 s |
Numbers are indicative for a typical React + TypeScript + CSS Modules project.
The dev server gap is dramatic for large projects. Production build gap is smaller but still significant.
When to use Vite
Vite is the right choice for:
| Scenario | Reason |
|---|---|
| New projects (React, Vue, Svelte, Solid) | Fastest setup, best DX out of the box |
| SPAs and PWAs | Excellent support, minimal config |
| Monorepo packages (libraries) | Rollup-based output ideal for tree-shakeable libs |
| Teams valuing fast iteration | Instant HMR dramatically improves productivity |
| Greenfield Next.js / Remix alternatives | Vite is the default bundler for many modern meta-frameworks |
| Projects starting fresh | No migration cost, clean config |
When to use Webpack
Webpack remains the right choice for:
| Scenario | Reason |
|---|---|
| Legacy browser targets | Decades of polyfill/transform tooling |
| Highly custom build pipelines | Fine-grained loader/plugin control |
| Server-side rendering with complex asset pipelines | Long-established SSR patterns |
| Existing large Webpack project | Migration cost may not justify switch |
| Module federation | Webpack Module Federation is mature; Vite's support is newer |
| Projects tightly coupled to CRA/Angular CLI | These use Webpack internally |
| Enterprise with existing Webpack expertise | Known tool, well-documented workarounds |
Migrating from Webpack to Vite
The migration is usually straightforward for standard projects. High-level steps:
1. Install Vite
npm install -D vite @vitejs/plugin-react
# Remove Webpack and its loaders/plugins
npm uninstall webpack webpack-cli webpack-dev-server babel-loader ...
2. Create 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') },
},
});
3. Update index.html
Vite uses index.html as the entry point (move from public/ to project root):
<!-- Add this script tag — Vite injects automatically -->
<script type="module" src="/src/main.tsx"></script>
4. Update environment variables
// Before (Webpack + dotenv-webpack)
process.env.REACT_APP_API_URL
// After (Vite)
import.meta.env.VITE_API_URL // rename .env vars to VITE_ prefix
5. Replace require() with import
Vite uses native ESM — CommonJS require() calls in your source code need to be converted to import. Dependencies that use CJS are handled automatically.
// Before
const logo = require('./logo.svg');
// After
import logo from './logo.svg';
6. Update npm scripts
{
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
}
}
Common migration blockers
| Issue | Solution |
|---|---|
process.env.* references |
Replace with import.meta.env.VITE_* |
require() in source |
Convert to ESM import |
| Webpack-specific loaders | Find Vite/Rollup plugin equivalent |
| Module Federation | Use vite-plugin-federation |
| Custom Webpack plugins (no Vite equiv) | May need to rewrite or keep Webpack |
| Jest + Webpack | Switch to Vitest (built for Vite) |
| Large CSS-in-JS libraries | Usually work; test thoroughly |
Full comparison table
| Feature | Vite | Webpack |
|---|---|---|
| Dev startup speed | < 1 s | 10–120 s |
| HMR speed | 20–50 ms | 1–10 s |
| Production bundler | Rollup | Webpack |
| Production build speed | Fast (esbuild min) | Medium |
| Config verbosity | Low | High |
| TypeScript | Built-in (transpile only) | Via loader |
| Type checking | Separate (tsc --noEmit) |
Via ts-loader or fork-ts-checker |
| CSS Modules | Built-in | Via css-loader |
| CSS preprocessors | Auto-detected (install sass/less) | Via loaders |
| Static assets | Built-in | Via asset/* rules |
| Code splitting | Automatic | Manual config |
| Tree-shaking | Excellent | Good |
| Legacy browser support | Plugin needed | Excellent built-in |
| Module Federation | vite-plugin-federation |
Native (mature) |
| SSR support | Built-in API | Complex config |
| Plugin ecosystem | Growing | Massive |
| Learning curve | Low | High |
| CRA replacement | Yes (official Vite templates) | CRA uses Webpack |
| Testing integration | Vitest (native) | Jest (separate config) |
| Framework support | React, Vue, Svelte, Solid, Qwik | Any (via loaders) |
Common mistakes
| Mistake | Why it's a problem | Fix |
|---|---|---|
Forgetting VITE_ prefix for env vars |
Variables silently undefined in browser | Rename all public env vars to VITE_* |
Using process.env in Vite |
Not defined by default | Use import.meta.env |
require() in Vite source |
Native ESM doesn't support it | Convert to import |
| Assuming type errors fail build | esbuild strips types, doesn't check | Run tsc --noEmit in CI |
Not running vite preview before deploy |
Dev and preview servers behave differently | Always test vite build && vite preview |
| Migrating Webpack Module Federation to Vite | vite-plugin-federation has limitations |
Test thoroughly or keep Webpack for that service |
| Skipping Rollup docs | Vite production config uses Rollup options | Read both Vite and Rollup docs |
Using index.html in public/ |
Vite expects it in project root | Move it up one level |
Vite vs Webpack vs other build tools
| Tool | Approach | Best for |
|---|---|---|
| Vite | ESM dev server + Rollup prod | SPAs, modern libraries, fast DX |
| Webpack | Full bundle (dev + prod) | Legacy apps, fine-grained control, Module Federation |
| esbuild | Ultra-fast bundler (Go) | CLI tools, build scripts (no HMR) |
| Rollup | ES module bundler | Libraries (tree-shakeable output) |
| Parcel | Zero-config bundler | Rapid prototyping |
| Turbopack | Incremental bundler (Rust, Next.js) | Next.js 15+ (still maturing) |
| Rspack | Webpack-compatible (Rust) | Drop-in Webpack replacement, faster |
FAQ
Is Vite production-ready?
Yes. Vite 5 is stable and used by major projects including Vue 3, SvelteKit, Astro, Remix (via @remix-run/dev), and thousands of production apps. The production bundle uses Rollup, which has been production-ready for years.
Does Vite replace webpack? For new SPAs and modern frontend projects: yes, Vite is the better default. For existing Webpack projects, legacy browser requirements, or Module Federation: Webpack still has advantages. Vite doesn't replicate every Webpack feature.
Is Vite faster than Webpack in production builds too? Yes, typically 3–5× faster due to esbuild's minification and Rollup's efficient tree-shaking. The gap is less dramatic than in development, but still meaningful in CI pipelines.
Can I use Vite with Jest?
You can, but it's awkward. The idiomatic choice is Vitest, which reuses your vite.config.ts, shares your plugins and aliases, and is ~2–3× faster than Jest for typical TS/React projects.
Does Vite support SSR? Yes. Vite has a built-in SSR API. Meta-frameworks like SvelteKit, Astro, and Nuxt 3 use it. For React SSR, consider Vite-based frameworks like Remix or manual Vite SSR.
Should I migrate my existing Webpack project to Vite? Depends on project size and pain points. If your dev server is slow and the project is a standard SPA, migration is usually worth it (1–3 days for a medium project). If you use Module Federation, custom loaders, or legacy browser targets, evaluate carefully before migrating.