When the Build Becomes the Problem
There's a particular kind of technical debt that's easy to ignore because it doesn't break anything — it just makes everything slower and slightly worse. Aging Webpack configs fall into this category. They work, technically. But they accumulate complexity over time, nobody fully understands them anymore, and they impose a hidden tax on every developer who has to wait through a build.
At Guild Mortgage, eight production single-page applications had reached this point. Bundle sizes were bloated — some pushing 6MB. Cold-start build times were slow enough to interrupt flow. And the developer experience had become a low-grade source of daily friction that nobody was addressing because the path forward wasn't obvious.
We made it obvious.
Why Vite
The decision to migrate to Vite rather than optimize the existing Webpack configs came down to one core insight: Webpack's approach to bundling made it inherently difficult to reason about what was being included and why. Dependencies were opaque. Tree shaking was inconsistent. The tooling required to diagnose problems (webpack-bundle-analyzer, custom plugins) added more configuration on top of an already complex setup.
Vite's native ES module support inverts this. In development, dependencies are served as individual modules — you can see exactly what's being pulled in. That transparency alone changed how the team reasoned about performance. It also made Vite's production builds meaningfully faster because the optimization surface was cleaner from the start.
How We Approached It
The first step was measurement, not cutting. We ran webpack-bundle-analyzer across all eight applications to get a precise picture of where the weight was coming from before touching anything. This prevented the trap of optimizing things that didn't matter while missing the things that did.
The findings were predictable once we looked: large routes loading everything upfront, whole-library imports pulling in far more than was needed, and assets being bundled that had no business being in the JavaScript payload at all.
From there, the remediation was systematic:
Route-level code splitting using React.lazy() and Suspense — large routes only loaded when actually navigated to. This alone had an outsized impact on initial load time.
Targeted imports replaced whole-library imports. Whole-library pulls like lodash were swapped for targeted ones like lodash/debounce. Every import became intentional.
moment.js was replaced entirely with date-fns — a significantly lighter library that's tree-shakeable by design. Moment had been the right choice once. It hadn't been for a long time.
Large assets — images, SVGs that didn't need to be inlined — were moved to the public folder rather than processed through the bundle.
Production mode was enforced across all builds. This sounds obvious, but enforcing it consistently activated automatic tree-shaking that had been silently disabled in some environments.
The Outcome
Bundles that sat at 6MB dropped to under 2MB. Cold-start build times fell sharply. Core Web Vitals improved across all eight applications. And perhaps most importantly, the team could iterate quickly without dreading the build step — that hidden daily friction disappeared.
All eight applications were migrated with zero downtime.
What This Was Really About
The technical work here was straightforward. The harder part was making the call to invest in it — to treat the build pipeline as a product worth improving rather than infrastructure to be tolerated. Developer experience is a performance metric. When engineers can move faster and with more confidence, better software ships more often.
The Webpack-to-Vite migration was worth doing. It would have been worth doing a year earlier.
