Skip to content

How to migrate from Webpack to Vite

How do you migrate a webpack project to Vite?

ModerateEstimated time: 1-3 daysNo data lossFrom: webpack To: vite

Why migrate from Webpack to Vite?

The pitch: Vite uses the browser's native ES modules during development (no bundling) and esbuild for prebundling dependencies. Cold dev-server start drops from "minutes" to "sub-second"; HMR updates land in <100ms regardless of project size. The production build switches to rollup, which produces output competitive with webpack.

For a typical React app moving from webpack 5, the wins are:

  • Dev start: 30s -> 0.5s. The biggest single ergonomics win in modern frontend tooling.
  • HMR: 2-5s -> <100ms. No more "save and lose your edit cursor while waiting".
  • Config surface: 200 lines -> 20 lines. Vite's defaults cover what most apps need; bring-your-own-loader is rare.
  • Production bundle: roughly the same. Both use tree-shaking, code-splitting, and minification. Don't migrate for production bundle size -- migrate for dev-loop speed.

The trade: less mature plugin ecosystem. webpack has a plugin for everything; Vite has plugins for the common 90% and sometimes a webpack-specific tool (Module Federation, custom loaders) won't have a direct Vite equivalent.

Before you start

  • Pin your current webpack config to git. You'll reference it during the move and want to be able to roll back.
  • Inventory your loaders + plugins. List every entry in module.rules and plugins. Each needs a Vite equivalent or removal decision.
  • Check Node version. Vite 5+ requires Node 18+.

Step-by-step migration

1. Install Vite + your framework plugin

npm install --save-dev vite
# Pick one based on your framework:
npm install --save-dev @vitejs/plugin-react      # React
npm install --save-dev @vitejs/plugin-vue        # Vue
npm install --save-dev @sveltejs/vite-plugin-svelte

2. Create vite.config.js

Minimal React example:

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'node:path';

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },
  server: {
    port: 3000,
  },
});

Port your webpack resolve.alias here. Port your webpack devServer.proxy to Vite's server.proxy.

3. Move index.html to project root

webpack's HtmlWebpackPlugin renders the index template; Vite just serves an index.html from the project root that imports your entry script directly:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <title>My App</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

Move whatever was in your webpack template's <head> here.

4. Update package.json scripts

"scripts": {
  "dev": "vite",
  "build": "vite build",
  "preview": "vite preview"
}

Drop webpack-dev-server, webpack-cli, and the loader/plugin devDependencies in the same commit -- otherwise they sit around adding to install time.

5. Replace webpack-specific imports

These webpack idioms don't work in Vite + need replacements:

webpack Vite
require.context('./svg', false, /\.svg$/) import.meta.glob('./svg/*.svg')
require('./img.png') new URL('./img.png', import.meta.url)
process.env.MY_VAR import.meta.env.MY_VAR (must be VITE_-prefixed)
dynamic-import-string-template plain import() with literal paths

Search-and-replace require.context and process.env first -- they're the most common sources of "works in dev, breaks in build" issues post-migration.

6. CSS / preprocessor handling

Vite supports .css, .scss, .sass, .less, .styl out of the box -- just npm install the preprocessor and import. CSS Modules work via .module.css filename convention.

If you used MiniCssExtractPlugin, it's gone -- Vite does this in production by default.

7. Run dev + production builds

npm run dev     # should start in <1s
npm run build   # produces dist/
npm run preview # serves dist/ for spot-check

Walk through the app. Most issues surface as one of:

  • Missing alias (add to resolve.alias).
  • Missing env var (rename MY_VAR -> VITE_MY_VAR + update references).
  • require() in a third-party CommonJS dep (Vite's optimizeDeps.include helps).

What doesn't migrate automatically

  • Module Federation. Webpack's headline feature for micro-frontends. Vite has originjs/vite-plugin-federation but it's not 1:1.
  • Custom webpack loaders. Audit module.rules for anything beyond the common loaders -- Vite plugins differ in shape.
  • Service worker config (Workbox webpack plugin). Use vite-plugin-pwa instead.
  • MDX with custom remark/rehype plugins. @mdx-js/rollup is the equivalent.

Common pitfalls

  • Env vars stop appearing. Vite requires VITE_ prefix on env vars (the prefix is configurable via envPrefix). A process.env.API_URL reference will silently render undefined in the browser bundle until renamed.
  • CommonJS dependencies break in dev. Some third-party libs ship CJS only. Add them to optimizeDeps.include so Vite pre-bundles them as ESM.
  • Tree-shaking changes break a side-effecty import. Vite's rollup is more aggressive than webpack's terser. If a side-effect-only import (import 'side-effect-css';) gets shaken out, mark its package's sideEffects: true in package.json.
  • CSS import order. webpack and rollup order CSS imports differently. If your design depends on cascade order, audit the built dist/assets/index.css for unexpected reordering.

How BeaverCheck measures the difference

Avg performance: webpack vs viteAvg performance: webpack vs vite0255075100webpackvite

We've audited 901 sites built with webpack and 24 built with Vite. Average Lighthouse performance: webpack-built sites 38 / 100, Vite-built sites 49 / 100, a difference of +11 points.

Production-bundle quality is similar between the two; the gap that BeaverCheck measures mostly reflects what stack each site is ON, not the bundler itself. Run a free audit to see your current bundle's effect on Core Web Vitals.

Further reading

Send Feedback