Vite has become the default build tool across essentially every frontend project we run, but each package in our growing monorepo had accumulated its own slightly different config over time, small divergences in alias resolution, plugin lists, and build targets that nobody had deliberately decided on, they'd just crept in as each package was added by whoever happened to be setting it up that week.
Extracting a shared base config that each package extends, overriding only what's genuinely package-specific, brought real consistency back without forcing every package into an identical setup that wouldn't fit all of them. The base config handles the things that should always be the same across the monorepo, TypeScript path alias resolution, our standard set of plugins for SVG handling and environment variable injection, and shared build output conventions, while leaving things like entry points and package-specific externals to each package's own config file.
Getting the override boundary right took a couple of iterations. Our first version of the shared config tried to cover too much, which meant packages with genuinely different needs, one package builds a library for npm publishing rather than an app for deployment, kept fighting the shared defaults rather than benefiting from them. Pulling the library-specific package's build target and externalization rules back out of the shared config and into that package's own file, rather than trying to make the shared config flexible enough to handle every case through options, ended up being the simpler and more maintainable fix.
The consistency payoff has been most visible in onboarding: a new engineer joining any package in the monorepo now finds a build setup that looks familiar from whatever other package they worked in previously, rather than needing to relearn slightly different conventions each time they move between packages. We also caught a couple of subtle alias resolution bugs during the consolidation that had been causing occasional confusing import errors in one package for months, simply because writing the shared config forced us to actually compare every package's settings side by side for the first time.
We'd recommend this kind of consolidation to any monorepo where packages have grown their configs independently over more than a year or so; the actual work is straightforward, but the value of having done it compounds every time someone new touches a package they haven't worked in before.
Rollout across the monorepo happened package by package rather than all at once, partly out of caution and partly because we wanted each migration to serve as a small validation step before touching the next package. We started with two of our smaller, lower-traffic packages, migrated them to extend the shared base config, ran their full test and build pipelines to confirm nothing regressed, and only then moved on to the packages other teams depend on more heavily day to day. That staged approach meant any issue with the shared config itself surfaced on a low-stakes package first rather than on whichever package happened to migrate first by chance.
One thing we hadn't anticipated: a couple of packages had been relying, likely accidentally, on subtly different dependency resolution behavior that came from their own bespoke config rather than anything intentional. Standardizing on the shared config surfaced a phantom dependency in one package, a module it was importing that worked only because of how that package's old, divergent alias configuration happened to resolve it, which needed an explicit dependency added once the shared config's stricter resolution rules stopped papering over the gap. Finding that kind of hidden coupling before it caused a harder-to-diagnose production issue was an unplanned but genuinely valuable side effect of the consolidation work.