Migrating a 150-Component ERP from CRA to Vite
Dev server restarts went from 3 minutes to 300ms. The migration took a weekend and taught me more about module graphs than I wanted to know.
Why Bother?
Create React App was the natural default when the project started, and for the first year or two it did its job without drawing attention. Three years and 150-plus components later, the dev server had accumulated dozens of dependencies and a Webpack configuration that nobody wanted to touch. Dev server restarts routinely took close to 3 minutes — which doesn't sound catastrophic until you realize that restarting the dev server is something a developer does dozens of times a day. Three minutes per restart multiplied by a dozen restarts per day multiplied by a dozen developers is a substantial productivity tax that accumulates silently. Hot module replacement had become unreliable enough that most of us just refreshed the whole page out of habit, defeating the purpose of HMR entirely.
CRA was also effectively unmaintained upstream, and the official React documentation had shifted its recommendation toward Vite for plain SPAs. Staying on an unmaintained build tool is a long-term liability — security patches stop flowing, compatibility issues accumulate, and the pool of engineers familiar with the tooling shrinks. For an internal ERP that didn't need server-side rendering, Vite was the only serious option. Next.js would have been overkill for a single-tenant internal app, and Remix's loader model didn't fit our existing data-fetching patterns.
The Migration
Vite uses Rollup for production builds and serves native ES modules directly to the browser during development, which is why its dev server starts so much faster. Webpack has to bundle the entire application before serving; Vite serves modules unbundled, letting the browser load the module graph on demand. The result is that dev server startup scales with the size of the entry point, not the size of the application. The configuration came out to roughly 20 lines, compared to the 300-plus lines of accumulated Webpack config we were replacing — and once it was in place, the dev server started in under a second with HMR that actually felt instant.
The migration itself wasn't the hard part — the edge cases were. Dynamic imports that relied on Webpack-specific magic comments stopped working. SVG imports needed a different plugin and import pattern. Environment variable handling differed enough that every `process.env.REACT_APP_*` reference needed to be tracked down and updated to Vite's `import.meta.env` convention — and some references had been copy-pasted into strings and were only discoverable by grepping the entire codebase. A few CommonJS-only dependencies needed explicit interop configuration, and one dependency used `require()` internally in a way that broke Vite's ESM-first model entirely and had to be replaced.
The Result
Dev server startup dropped from roughly 3 minutes to about 300 milliseconds. Full production build time fell from 90 seconds to about 15. Bundle size dropped by roughly 12% thanks to Rollup's more aggressive tree-shaking — a pleasant surprise, since we weren't shipping meaningfully different code. For a system deployed to low-end devices over unreliable connections, every kilobyte saved translates to faster page loads for real users, so a 12% reduction for free is meaningful. Beyond the raw numbers, the team's relationship with the build tooling changed — people stopped avoiding restarts and stopped dreading the first build of the morning. Fast tooling doesn't just save time; it changes how engineers work, making them more experimental and less likely to settle for the first thing that works.
The whole migration took a single weekend. If a team is still shipping a React SPA on Create React App today, they're paying an ongoing productivity tax that a Vite migration removes almost entirely, for a comparatively small one-time cost. The migration is also a good example of the kind of investment that's hard to justify on a feature-by-feature basis — no user-visible feature comes out of it — but that pays for itself many times over in developer productivity. Making the case for that kind of work is part of a senior engineer's job, and concrete before-and-after numbers are what makes the case land.
