Moving a project that has grown around webpack to Vite is the most direct way to shrink your config surface and speed up dev server startup. This guide maps every piece of a webpack config (entry, output, loader, plugin, alias, env) to its Vite equivalent and walks through the migration step by step; the only sources used are the official Vite and webpack documentation.
💡 Pro Tip: Before you start the migration with npm install vite --save-dev, keep a full copy of your webpack config file on a separate branch — you'll need to come back to it as a reference while mapping loaders and plugins.Table of Contents
- Why Migrate: Dev Server and Build Difference
- Inventory: What's in Your webpack Config
- Environment Variable Mapping
- Steps 1-6: Migration
- Step 1 — Swap Packages
- Step 2 — Move index.html to the Root
- Step 3 — Move Environment Variables to the VITE_ Prefix
- Step 4 — Migrate Alias/Resolve Settings
- Step 5 — Simplify CSS/Asset Imports
- Step 6 — Legacy Browser Support
- Common Breakages
- Testing: Dev vs Prod Build Differences
- Rolldown/Vite Version Note
- When NOT to Migrate
- FAQ
- How do you migrate a webpack project to Vite?
- What breaks most often when migrating to Vite?
- When is Vite not a better choice than webpack?
- What happens if I forget the VITE_ prefix?
- Update (September 2026)
- Conclusion
- Sources
Why Migrate: Dev Server and Build Difference
Vite's official "Getting Started" page defines the tool as two parts: a dev server that serves native ES modules with rich features, and a build command that bundles code for production. The "Why Vite" page puts it this way: dependencies are pre-bundled once, while the project's own source code is served directly to the browser over native ESM — a fundamentally different approach from webpack's "process everything into one bundle graph first, then serve" model.
On the production side, starting with Vite 8 the build command bundles code with Rolldown. Rolldown's own site describes this as "the unified bundler powering Vite 8+" that "handles tens of thousands of modules without breaking a sweat."
In practice: the server you open with npm run dev no longer processes files into one graph up front, it compiles on demand — that's the difference you'll feel on first startup in large projects. The vite CLI reflects this too: vite dev and vite serve are aliases for plain vite.
Inventory: What's in Your webpack Config
Before you start migrating, inventory your webpack config against the six core concepts on the official "Concepts" page: Entry, Output, Loaders, Plugins, Mode, Browser Compatibility. webpack's default entry point is ./src/index.js, and its default output is ./dist/main.js (+ the ./dist folder). The table below shows the Vite equivalent of each concept.
Don't skip this inventory step — most migration friction comes from copy-pasting the config directly instead of answering, one by one, "which webpack feature maps to which Vite mechanism." This especially matters for project-specific steps nested deep in your loader chain (e.g., a custom SVG-to-component loader or a build-time code generator) — check which package provides each step and whether it has an official Vite plugin compatible with the Vite plugin API (compatible with the Rollup plugin interface). If not, moving that step into a separate Node script that runs before the build (prebuild) is often less risky than writing a Vite-specific plugin.
webpack Concept | Vite Equivalent |
|---|---|
Entry ( ./src/index.js) | index.html at the project root, points to the entry via <script type="module" src="..."> |
Output ( ./dist/main.js) | build.outDir (default dist) |
Loaders (css/file/babel-loader) | Most are natively supported (CSS, JSON, asset imports); the rest need a Vite/Rollup-compatible plugin |
Plugins | Vite plugin API (compatible with the Rollup plugin interface) |
Mode (development/production) | the --mode flag + import.meta.env.MODE |
Browser Compatibility | @vitejs/plugin-legacy |
Environment Variable Mapping
Vite's env page is clear: only variables prefixed with VITE_ leak into client code via import.meta.env; everything else stays server-side. File load order by priority is: .env → .env.local → .env.[mode] → .env.[mode].local — the mode-specific file takes priority over the general one. import.meta.env.MODE, the base URL constant, and .PROD, .DEV, .SSR also come as built-in constants.
Steps 1-6: Migration
Step 1 — Swap Packages
Remove webpack, webpack-cli, webpack-dev-server, and related loaders, and add Vite:
1npm uninstall webpack webpack-cli webpack-dev-server2npm install vite --save-devUpdate your package.json scripts: replace webpack serve with vite, and webpack --mode production with vite build.
Step 2 — Move index.html to the Root
In webpack projects, index.html usually lives under public/ and the bundler injects the script tag after the build. In Vite, index.html stays at the project root and you point to the entry directly yourself:
1<!doctype html>2<html>3 <body>4 <div id="root"></div>5 <script type="module" src="/src/main.tsx"></script>6 </body>7</html>The key part is marking the <script> tag with type="module" — the Vite dev server catches the request as native ESM this way and transforms the file on the fly. This can feel unfamiliar coming from webpack: no plugin like HtmlWebpackPlugin injects the script tag for you — you write it yourself, pointing to the source.
Step 3 — Move Environment Variables to the VITE_ Prefix
Rename the variables you read via process.env. to import.meta.env. with the VITE_ prefix (unprefixed variables don't leak to the client — a deliberate security boundary). While doing this, list every process.env. call in your project one by one; don't accidentally prefix secrets that need to stay server-side (API keys, database connection strings), because every prefixed variable gets embedded in the build output and becomes readable from the browser.
Step 4 — Migrate Alias/Resolve Settings
Rewrite your webpack resolve.alias entries as resolve.alias inside vite.config.ts:
1import { defineConfig } from "vite";2import { fileURLToPath } from "node:url";3 4export default defineConfig({5 resolve: {6 alias: {7 "@": fileURLToPath(new URL("./src", import.meta.url)),8 },9 },10});(In CJS-based projects coming from webpack where package.json doesn't have "type": "module", __dirname works directly; but since vite.config.ts is usually loaded as ESM, the fileURLToPath pattern above is safer.)
Step 5 — Simplify CSS/Asset Imports
Vite automatically injects .css imports via a <style> tag and provides HMR support; files with the .module.css extension are automatically processed as CSS Modules. Most of webpack's style-loader + css-loader + css-loader?modules chain becomes unnecessary at this step — you can delete it from your config.
Step 6 — Legacy Browser Support
If you're targeting older browsers, add @vitejs/plugin-legacy (the current version on the npm registry is 8.2.3, published August 6, 2026). This is the Vite equivalent of the webpack + Babel + browserslist chain:
1import { defineConfig } from "vite";2import legacy from "@vitejs/plugin-legacy";3 4export default defineConfig({5 plugins: [legacy({ targets: ["defaults", "not IE 11"] })],6});Once you've completed these six steps, I'd recommend not deleting your webpack config right away — running both side by side for a few weeks (e.g., keeping the old build script as npm run build:legacy) and comparing output sizes is the safest way to confirm the migration is truly complete. Delete the old config too soon, and a forgotten loader chain becomes much harder to notice.
Common Breakages
require()calls: Vite is built on native ESM; CommonJS-only dependencies are converted during the dependency optimizer step. In Vite 8 this conversion is now done with Rolldown instead of esbuild — the official migration page says "Rolldown is now used for dependency optimization instead of esbuild."process.env.reads: Only variables with theVITE_prefix are carried to the client; unprefixed ones returnundefined. Don't prefix server-only secrets — this is a deliberate leak-prevention rule.- Dynamic import globs: webpack's
require.contextpattern is covered byimport.meta.glob()in Vite; it's not the exact same API, so you'll need to rewrite your glob pattern when migrating. - TypeScript type-checking expectations: Vite only transpiles
.tsfiles, it doesn't perform type checking (official wording: "does NOT perform type checking")."isolatedModules": trueis required intsconfig.json, because the Oxc transformer works without type information. - Use of
optimizeDeps.esbuildOptions: This field is deprecated; migrating tooptimizeDeps.rolldownOptionsis the officially recommended path.
The common thread here is that none of these breakages shows up as a clear build error — most fail silently by returning undefined (env variables), work in dev but break at build time (dynamic globs), or only surface when tsc runs separately (isolatedModules mismatches). Don't rely on just a "build succeeded" check in your migration PR — add a manual test step for each breakage point.
Testing: Dev vs Prod Build Differences
Because Vite's dev server and vite build output go through different paths (dev: on-the-fly transformation over native ESM; build: bundling with Rolldown), you need to verify both environments separately. The official docs note that type errors aren't caught during dev and recommend a separate step before the production build: "For production builds, you can run tsc --noEmit in addition to Vite's build command." Add this to CI:
1tsc --noEmit && vite buildYou can use the preview command in Vite's CLI to verify the build output locally:
1vite build2vite previewThis step is useful for catching imports that only break in the build output but work fine on the dev server (e.g., dynamic glob patterns, incorrect alias resolution); it's recommended to add it to CI alongside tsc --noEmit.
Rolldown/Vite Version Note
As of September 16, 2026, the current vite version on npm is 8.3.0 (released September 10, 2026); Vite 8.0.0 first shipped March 12, 2026. Per the migration guide, Vite 8 uses Rolldown- and Oxc-based tooling instead of esbuild and Rollup: dependency pre-bundling with Rolldown, JavaScript transformation and minification with Oxc.
When NOT to Migrate
The following three situations add extra engineering overhead to the migration:
- A heavy CommonJS dependency graph that can't be moved to native ESM: The dependency optimizer tries to convert these, but deep, circular
require()chains can require manual intervention. - TypeScript patterns incompatible with
isolatedModules: type-information-dependent features likeconst enumor certain namespace usages may not work directly with Oxc's transpile-only model; test your project withtsc --noEmitfirst. - Highly specialized webpack loader chains: if your build steps depend on in-house, undocumented loaders, do a separate proof-of-concept before migrating them to the Vite plugin API.
What these situations have in common: the migration isn't technically impossible, it just adds extra overhead. If you accept and plan for that upfront, the migration can still make sense; the risky move is starting without noticing any of these and ending up halfway through with a config split in two.
GOLDEN TIP
The most valuable insight in this article
This tip holds the article's most important takeaway.
Easter Egg
You found a hidden gem!
There's a hidden detail in this section. Want to uncover it?
Reader Reward
I've put together, in a single checklist, the items you should check in your project before starting the migration — you can copy this list into your migration PR's description and check it off step by step.
FAQ
How do you migrate a webpack project to Vite?
First inventory your webpack config by entry/output/loader/plugin/mode, then install vite, move index.html to the project root, convert process.env. reads to import.meta.env. with the VITE_ prefix, rewrite aliases as resolve.alias in vite.config.ts, and add @vitejs/plugin-legacy if needed. Starting from the most isolated module and verifying with vite build + tsc --noEmit is safer than migrating everything at once.
What breaks most often when migrating to Vite?
The most common breakages are: unprefixed process.env. reads (don't leak to the client, return undefined), dynamic import globs written with require.context (need to be rewritten as import.meta.glob()), certain TypeScript patterns that depend on type information (due to the isolatedModules: true requirement), and deep CommonJS dependency chains.
When is Vite not a better choice than webpack?
Official sources don't make a direct counter-recommendation; however, if your project depends on highly specialized, undocumented webpack loader chains, or uses TypeScript patterns incompatible with isolatedModules, measure the risks with a small proof-of-concept module before migrating.
What happens if I forget the VITE_ prefix?
The variable never reaches the client code at all — it shows up as undefined in import.meta.env; this isn't a build error, it's a silent behavior difference, which is why it's important to add the env migration step to your checklist.
Update (September 2026)
Three points came up while preparing this guide that needed verification during September 2026; I'm adding them below along with their sources:
- Vite 8.3.0 (September 10, 2026): A minor feature + fix release; doesn't affect the migration steps. Notable changes from the changelog: preload dependencies already seen during the build are no longer reprocessed for performance (#23446), only full
node_modulespath segments count as dependencies (#23437), proxy context matchers are precompiled when the server is created (#23263), and CRLF line endings are now handled correctly in code-frame positions (#23219). Source: github.com/vitejs/vite/releases. - The
rolldown-vitepackage is archived: the repo appears archived on GitHub, but Vite's official migration guide still lists therolldown-vitepackage as an optional intermediate step when migrating to Vite 8; going straight to the current Vite 8.x is also a valid path. Source: github.com/vitejs/rolldown-vite, vite.dev/guide/migration. - webpack 6 hasn't shipped yet: if you run into an assumption of "webpack 6" while reading this guide, it's out of date — webpack's current major version is still the 5.x series (5.111.0, September 14, 2026). Source: registry.npmjs.org/webpack.
Conclusion
Migrating from webpack to Vite doesn't mean rewriting your codebase — it means remapping your config across six core concepts (entry, output, loader, plugin, mode, browser compatibility). The three most critical points: moving env with the VITE_ prefix, TypeScript compatibility via isolatedModules: true, and moving index.html to the root. Progressing module by module and verifying each step with tsc --noEmit + vite build keeps surprises small. For a feature-level comparison table of the two tools, see the webpack vs Vite page.
For similar build/runtime changes on the Next.js side, see Next.js 16.3 Instant Navigations and Cache Components Guide and Next.js 16.3 Retires runtime='edge': Back to Node. For the parallel shift on the TypeScript compiler side, see TypeScript 7 Ships: 10x Faster But ESLint Breaks. If you're updating your database layer too, see Prisma 6 to 7 Migration: The Rust Engine Is Gone, What Changed?. For a similar build-pipeline modernization on iOS, see Xcode Cloud Pipeline Optimization: Build Times, Caching and Cost 2026 (in Turkish).
Sources
- Vite — Getting Started — official definition of the dev server and build command, the
vite dev/vite builddistinction - Vite — CLI — source for
vite devandvite servebeing aliases for thevitecommand - Vite — Migration from v7 — Vite 8's Rolldown/Oxc migration, browser target change, deprecated APIs
- Vite — Features — CSS/CSS Modules imports, TypeScript transpile-only behavior,
isolatedModulesrequirement - Vite — Env Variables and Modes — the
VITE_prefix rule,.envfile priority order, built-in constants - Vite — Why Vite — qualitative explanation of dependency pre-bundling and the native ESM serving model
- Rolldown — Home — the "unified bundler powering Vite 8+" description
- webpack — Concepts — Entry, Output, Loaders, Plugins, Mode, Browser Compatibility concepts and defaults
- npm — vite package — current version and release dates (8.3.0, 8.0.0)
- npm — @vitejs/plugin-legacy package — current version of the legacy browser plugin
- Vite — Release Notes (GitHub) — 8.3.0 maintenance release changelog
- rolldown-vite — GitHub — note on the intermediate package's archived status
- webpack — npm registry — webpack's current version number and release date

