Build tool logos and fast lightning suggesting Vite dev server performance
8 min readVue 3 Migration

Webpack to Vite Mid-Migration: How to Cut Build Times Without Rewriting Your App Twice

Vite’s dev experience is a morale multiplier. It is also not a drop-in rename of your Webpack config. The failure mode: switch bundlers, fight obscure resolution bugs, and still be on @vue/compat—so you moved two mountains at once. Better patterns: stabilize Vue 3 in dev with a known good Webpack (or align tooling), then move to Vite, or do Vite first for faster feedback on the Vue 3 branch if your plugins already have first-class Vite support. Choose based on what blocks you today, not on hype.

1. import.meta.env vs process.env

Map env usage deliberately; a half-migrated define setup is a source of “works in local, 500s in preview.”

2. Aliases, static assets, and public/

Deep-link and PDF flows often break on base path changes. Verify staging URLs match production routing.

3. Use faster CI as the KPI

Time saved in npm test and typecheck loops funds more migration perf work. Measure before/after pipeline duration.

4. Sequencing: Vite first, Vue 3 first, or both at once?

The biggest decision is not how you switch bundlers, it is when. Doing both moves at once feels efficient on a slide. In a real codebase it usually means every regression has two suspects, and your git bisect sessions get long. We sequence based on what is actually blocking the team, not on which tool is trending.

Path A — Stabilize Vue 3 on Webpack, then Vite

Best when your existing Webpack config is mature, has custom loaders or DLL plugins, and your team’s muscle memory is in vue.config.js. You finish the framework migration on a known-good build, prove parity in production, then schedule the bundler move as its own ticket. Pairs well with a strangler fig rollout where Vue 3 routes are dropped in incrementally.

Path B — Vite first on the Vue 3 branch

Best when dev server cold-start is the bottleneck for the migration team itself, or your plugins (Pinia, Vue Router 4, Vitest) already have first-class Vite support. The fast feedback loop pays for itself within two weeks of refactor work.

Path C — Both at once

Only viable for small apps (under ~30k lines) or greenfield rewrites where the test suite gives you confidence. For everything else, treat it as the high-risk option, not the default.

5. Config parity: the unglamorous checklist

Most “mysterious” Vite bugs are config drift, not Vite itself. Walk through this list before you cut over staging:

  • Path aliases — Webpack resolve.alias needs to be re-declared in vite.config.ts and tsconfig.json paths. Mismatch breaks editor go-to-definition silently.
  • Env variables — anything reading process.env.VUE_APP_* needs migration to import.meta.env.VITE_* or a shim via define.
  • Static assets — files in public/ are served at root in both, but require()-style imports of images need to become explicit import url from './x.png?url'.
  • Base pathpublicPath becomes base; double-check sub-path deployments behind reverse proxies.
  • SCSS / global imports — Webpack’s prependData moves to Vite’s css.preprocessorOptions.scss.additionalData.
  • Polyfills — Vite assumes modern browsers in dev. If you support older targets, add @vitejs/plugin-legacy from day one or LCP regresses on real devices.

A side-by-side example for env handling:

// Webpack (vue.config.js)
process.env.VUE_APP_API_URL

// Vite (vite.config.ts)
import.meta.env.VITE_API_URL

// Compatibility shim during migration:
// vite.config.ts
define: {
  'process.env.VUE_APP_API_URL': JSON.stringify(process.env.VITE_API_URL),
}

6. Common pitfalls we see in real migrations

CommonJS dependencies in node_modules

Vite is ESM-first. A few legacy libraries still ship CJS only, and they will appear as “default export is not a function” at runtime. The fix is usually optimizeDeps.include plus, occasionally, @originjs/vite-plugin-commonjs. Audit your dependency tree before the cutover, not after.

Dynamic imports with template strings

Patterns like import(`./locales/${lang}.json`) work in Webpack with implicit context. Vite needs an explicit import.meta.glob pattern. This catches teams during vue-i18n locale lazy loading.

SSR mismatches

If you run SSR or static generation, the Vite SSR API differs from vue-server-renderer. Plan for a separate spike on the SSR pipeline; it is rarely a one-liner.

Test runners drifting

Jest doesn’t know about Vite’s transforms. Either keep Jest with its own babel config, or adopt Vitest at the same time — but not on the same PR. Our take is in the Vue Test Utils & Jest in 2026 piece.

7. Webpack vs Vite trade-off table

Vite is not unconditionally better — it is better for most modern Vue 3 apps. Knowing why helps you defend the schedule.

ConcernWebpack 5Vite 5+
Dev server cold start20–90s on large apps1–5s typical
HMR latency200–2000ms< 200ms
Production bundle sizeMature, fine-grained controlComparable; uses Rollup
Plugin ecosystem maturityLargest in JSGrowing, Rollup-compatible
Legacy browser supportBuilt inRequires plugin-legacy
Custom loadersExcellent, well-documentedNeed Rollup/Vite plugin equivalents
SSR ergonomicsManual, matureFirst-class via ssrLoadModule

8. Migration checklist for the bundler swap

Treat this as a runbook, not a vibe. Tick each item before the cutover PR is reviewed:

  • Inventory all vue.config.js options and find the Vite equivalent (or decide not to port).
  • Audit node_modules for CJS-only packages; add to optimizeDeps.
  • Replace dynamic require patterns with import.meta.glob.
  • Convert env vars to VITE_ prefix; add a shim if non-Vue services read them.
  • Run production build and diff bundle output against the Webpack baseline (size + chunk count).
  • Smoke-test deep links, OAuth callbacks, file uploads, and PDF/image flows.
  • Re-measure CI duration: install, typecheck, unit tests, build. The win should show up here.
  • Update local docs and onboarding scripts so new hires don’t install both bundlers “just in case.”

Treat the CI duration drop as the headline KPI. It is the metric that funds the next chunk of Core Web Vitals work.

9. FAQ

Should we migrate to Vite while still on Vue 2?

Generally no. Vue 2 + Vite works (via vite-plugin-vue2), but you would maintain an unusual stack for a few months and then change it again. Better to align the bundler swap with the Vue 3 branch, or keep Webpack until Vue 3 is in production.

Will our bundle size shrink?

Usually slightly, because Rollup’s tree-shaking is aggressive and Vue 3 itself is more shakeable than Vue 2. Don’t promise large wins from the bundler alone — most LCP improvements come from code-splitting and lazy routes.

What about module federation?

If you rely on Webpack Module Federation in a micro-frontend setup, vet @originjs/vite-plugin-federation early — feature parity is close but not identical, and shared dependency strategies differ.

Do we need to change ESLint?

No, but it’s a good moment to align the parser with Vue 3. See ESLint, Prettier, and the Vue 3 compiler.

Conclusion

Webpack to Vite is a bundler migration; Vue 2 to 3 is a framework migration. Either can stand alone, but when combined, sequence deliberately so you are never debugging two root causes in one git bisect session.

Related