All Articles
CategoryFull-Stack
Reading Time
12 min read
Published
2026-09-16
Word Count
2,849words

Grab a coffee — this one is a deep dive!

Webpack to Vite Migration: 2026 Step-by-Step Guide

Summary

This step-by-step webpack-to-Vite migration guide covers config inventory, env variables, alias settings and the most common breakages, based on the official Vite and webpack documentation.

  • Vite's dev server serves native ESM on demand instead of pre-bundling everything, and Vite 8 bundles production builds with Rolldown instead of esbuild/Rollup.
  • Inventory your webpack config against six core concepts (entry, output, loaders, plugins, mode, browser compatibility), then migrate packages, index.html, env vars, aliases, CSS imports and legacy support in six steps.
  • The most common breakages are silent: unprefixed process.env reads, require.context globs, isolatedModules-incompatible TypeScript patterns and the deprecated optimizeDeps.esbuildOptions field.
  • Migrate module by module and verify each step with tsc --noEmit + vite build instead of moving the whole project at once.
Webpack to Vite Migration: 2026 Step-by-Step Guide

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

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:

bash
1npm uninstall webpack webpack-cli webpack-dev-server
2npm install vite --save-dev

Update 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:

html
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:

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:

ts
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 the VITE_ prefix are carried to the client; unprefixed ones return undefined. Don't prefix server-only secrets — this is a deliberate leak-prevention rule.
  • Dynamic import globs: webpack's require.context pattern is covered by import.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 .ts files, it doesn't perform type checking (official wording: "does NOT perform type checking"). "isolatedModules": true is required in tsconfig.json, because the Oxc transformer works without type information.
  • Use of optimizeDeps.esbuildOptions: This field is deprecated; migrating to optimizeDeps.rolldownOptions is 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:

bash
1tsc --noEmit && vite build

You can use the preview command in Vite's CLI to verify the build output locally:

bash
1vite build
2vite preview

This 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 like const enum or certain namespace usages may not work directly with Oxc's transpile-only model; test your project with tsc --noEmit first.
  • 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_modules path 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-vite package is archived: the repo appears archived on GitHub, but Vite's official migration guide still lists the rolldown-vite package 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

Tags

#vite#webpack#bundler#rolldown#typescript#migration#build-tools
Muhittin Çamdalı

Muhittin Çamdalı

Lead Mobile Engineer

Lead Mobile Engineer with 12+ years of experience. Expert in iOS, Android and cross-platform architectures with Swift, SwiftUI, Kotlin and Flutter. I build performant, user-friendly mobile apps.

Share