JavaScript

Vite vs. webpack: a real build-time comparison on a mid-sized app

JS

Vite's pitch is that native ES modules make dev-server startup nearly instant, no matter how large the app grows, because the browser itself resolves imports on demand instead of the bundler pre-building a single dependency graph up front. We tested that claim against our existing webpack setup on an internal tool with roughly 200 components, a codebase old enough to have accumulated a realistic amount of webpack-specific configuration over its lifetime.

The numbers that convinced us

Cold start dropped from several seconds under webpack, on the order of 8 to 12 seconds depending on how warm the disk cache was, to something close to instant under Vite, generally under half a second before the dev server was ready to serve the first request. The difference only grew more pronounced as we added more components to a synthetic stress-test version of the app; webpack's cold start time scales with the size of the dependency graph it has to bundle up front, while Vite's scales with almost nothing, since it only transforms a module the moment the browser actually requests it.

Hot module replacement felt noticeably snappier too, and this is the part that matters more day to day than the cold start number, since most development time is spent in the edit-save-see-result loop rather than restarting the dev server. A single-file CSS or component change reflected in the browser in well under 100 milliseconds under Vite, versus a full webpack rebuild step that, even with its own HMR pipeline, took closer to a second or two on this codebase once the dependency graph reached its full size.

Where webpack still has the edge

A few webpack-specific plugins in our existing config had no direct Vite equivalent yet and needed workarounds. A custom SVG-to-React-component loader we'd relied on for years took real effort to replace with Vite's plugin ecosystem, which is genuinely good but younger and thinner than webpack's, especially for anything niche. We also hit friction with a legacy CommonJS dependency that assumed a webpack-style module resolution and needed an explicit Vite config tweak to load correctly, a class of problem that's mostly solved for popular packages at this point but still shows up on older or less-maintained dependencies.

  • Cold dev-server start dropped from roughly 8 to 12 seconds under webpack to under half a second under Vite, with the gap widening as component count increased.
  • Hot module replacement for single-file changes landed in well under 100 milliseconds under Vite, versus one to two seconds for a comparable webpack rebuild on this codebase.
  • A handful of webpack-specific plugins, notably a custom SVG loader, had no drop-in Vite equivalent and needed to be rebuilt against Vite's plugin API.
  • Production builds under Vite, which uses Rollup under the hood rather than its own dev-server bundling approach, produced comparable bundle sizes to the existing webpack production build, so the win here is entirely a development-experience one rather than a shipped-bundle-size one.

How we're rolling this out across the rest of the team

For new projects, Vite is now our default, and every project we've kicked off since this comparison has started on it rather than webpack, with no one on the team asking to go back once they've felt the difference in daily development speed. For existing large webpack configs with deep plugin dependencies, like the internal tool we benchmarked here, migration is worth planning carefully rather than rushing; we budgeted a full week for this particular migration once we accounted for the plugin replacements, and a codebase with more custom webpack configuration than this one would likely need longer.

The build-time win alone would have justified the migration for us, but the secondary benefit, a noticeably simpler configuration file once the plugin replacements were sorted out, has mattered almost as much. Vite's defaults cover far more of what a typical app needs out of the box than webpack's do, which means less configuration to maintain and fewer surprises for a new team member trying to understand how the build actually works.

CI build times told a slightly different story

It's worth separating the dev-server experience, where Vite's win is dramatic, from the production CI build, where the picture is more modest. Vite's production build uses Rollup rather than its own dev-time ES module serving, and our CI pipeline's build step, which runs the production build rather than the dev server, dropped from around 90 seconds under webpack to around 65 seconds under Vite on this codebase, a real improvement but nowhere near the order-of-magnitude difference we saw in local dev-server cold starts. That's an important expectation to set with a team before a migration: the highly visible daily win is the local development loop, and a team migrating purely to speed up CI should size their expectations accordingly rather than assuming the dev-server numbers translate directly.

Migrating the SVG plugin, concretely

The custom SVG-to-React-component loader mentioned above is worth walking through briefly, since it's the kind of migration friction that doesn't show up until you're actually mid-migration. Under webpack, SVGs were imported directly as React components through a loader configured in `webpack.config.js`, with no special import syntax required at the call site. Vite's plugin ecosystem has an equivalent, `vite-plugin-svgr`, but its default export behavior differs slightly from the webpack loader's, requiring a named import in some cases where the webpack version allowed a default import, which meant a scripted find-and-replace across roughly 40 files that imported SVGs this way, followed by a manual visual check of each affected component to make sure the icon still rendered correctly. It took about half a day in total, small in isolation but the kind of detail that adds up across a handful of similar plugin replacements during a migration like this one.

Why Vite's dev server is fast, in plain terms

It's worth explaining the mechanism briefly, since "it's just faster" undersells why the architecture actually produces this result. Webpack has to build a full dependency graph and bundle every module the app might need before it can serve the first page, even in development, which is why cold start time scales with app size. Vite instead serves your source code over native browser ES module imports directly, transforming each file on demand only the first time the browser actually requests it, and pre-bundles third-party dependencies once with esbuild, which is itself written in Go and roughly an order of magnitude faster than JavaScript-based bundlers at the specific job of resolving and transforming dependency code. The practical effect is that Vite's dev server startup time is nearly flat regardless of how many of your own components exist, since it's not building a graph of them up front at all; it only touches the files a given page actually imports as the browser asks for them.

Memory usage during local development

Beyond raw speed, memory footprint during a long development session was a difference we hadn't set out to measure but noticed anyway. Webpack's dev server on this codebase would climb past a gigabyte of memory usage over a multi-hour session, especially with several HMR cycles and a couple of restarts, occasionally prompting a laptop fan to spin up during an otherwise ordinary afternoon of editing. Vite's dev server stayed noticeably leaner across an equivalent session, since it isn't holding a full bundled dependency graph in memory the way webpack's dev server does, and none of our developers reported the same fan-spinning symptom after switching. It's a soft, anecdotal data point rather than something we benchmarked rigorously, but it lines up with the architectural difference driving the cold start numbers too.

Editor integration and TypeScript language server behavior

Editor responsiveness, specifically how quickly TypeScript's language server reflects a change back as inline errors and autocomplete, improved alongside the dev server switch, though the two aren't directly the same mechanism. Vite's approach to only transforming files on demand meant our editor's TypeScript server, watching the same source tree, wasn't competing with a webpack process also rebuilding the full dependency graph on every save, and a couple of engineers reported autocomplete suggestions appearing noticeably faster once the migration was complete. We can't attribute the entire improvement to Vite alone, since editor performance depends on several other factors too, but it was a welcome secondary benefit nobody was specifically chasing during the migration.

What the migration script actually automated

The rollout checklist mentioned above eventually became a small internal script rather than a document, since a checklist that has to be manually followed correctly every time is exactly the kind of process that quietly drifts out of compliance. The script scans a project's existing webpack config for the handful of known problem patterns, the SVG loader, the CommonJS resolution tweak, a couple of alias configurations we'd hit before, and generates a starter `vite.config.js` with the equivalent Vite plugins already wired in, leaving only genuinely novel configuration for a developer to handle by hand. It's cut the second and third migrations down from roughly a week each to two or three days, mostly because the mechanical, previously-error-prone parts of the migration no longer depend on someone remembering every gotcha from the first migration correctly.

Rolling this out beyond the one internal tool

Having validated the numbers on this internal tool, we've since migrated two more actively developed internal projects to Vite and adopted it as the default starting point for anything new, reserving webpack only for a couple of older client projects with build customization deep enough that a migration isn't worth the risk this year. The rollout checklist we built out of this first migration, covering the SVG loader replacement, the CommonJS dependency workaround, and a short list of other webpack-specific patterns to watch for, has cut the migration time on each subsequent project meaningfully compared to the first one, since most of the friction turned out to be the same handful of issues recurring across projects rather than genuinely new problems each time.

← Back to the journal

Have a project in mind?
Let’s talk.

Tell us where you are and where you want to go. We'll map the fastest route between the two.

Currently accepting new clients