One build, every environment: runtime configuration for micro frontends
If your pipeline builds the same code once per environment, the artifact you tested is not the artifact you ship. Here is how to build once instead.

Look at your pipeline. If it builds staging and production separately from the same commit, you are shipping an artifact nobody tested. You tested the staging one.
Most of the time this is fine, right up until the day it is not — a variable spelled differently in one environment, a flag that defaulted the other way, a bundle that behaved because of something only the staging build had. The bug appears in production only, and the investigation starts with the least useful sentence in software: it works on staging.
Why does each environment get its own build?
Because the configuration is inside the bundle. Somewhere in the code there is an API base URL, a feature flag, a tenant id, and at build time a bundler replaced it with a literal string. Change the value, and you have changed the bundle, so you build again.
For a single application this is merely wasteful. For micro frontends it compounds: every micro frontend, times every environment. Five micro frontends and three environments is fifteen builds of code that was identical in all fifteen.
What does it actually cost?
- The test result stops transferring. Whatever CI proved about the staging artifact, it proved about that artifact. The production one was built later, separately, from configuration nobody exercised.
- Promotion becomes a rebuild. Moving a version from staging to production is not a promotion any more, it is a new build — with a new chance to fail, on the day you least want one.
- Rollback gets slower. The old artifact for this environment may not exist any more. If it does not, rolling back means building it again, which means the rollback is a deploy.
- Configuration drifts silently. Three sets of environment variables in three places, and no single view of what actually differs. The drift is invisible until it is an incident.
What does runtime configuration actually mean?
It means the bundle contains no environment-specific value at all. Instead, on startup, the application asks for them and gets back a small document — a plain JSON object with the API base URL, the flags, whatever else differs.
The bundle is then genuinely identical everywhere. The same file, the same hash, the same artifact that CI tested, served to staging and production alike. What differs between environments is one document, and that document is data rather than code.
How does the browser get the configuration?
The host fetches it before it mounts anything. In practice this is one request early in the boot sequence, its result handed to whatever the application uses for configuration, and only then does rendering start.
Two details are worth getting right. Keep the response small, because it sits on the critical path of the first paint. And keep the cache short — a configuration cached for an hour at the edge is a configuration you cannot change for an hour, which quietly undoes the point.
What about secrets?
There are none here. Anything the browser can read is public, whether it arrived in a bundle or a JSON document: a value shipped to a client is a published value. Runtime configuration changes when a value is decided, not who can see it.
What belongs in it is the non-secret configuration that varies: base URLs, public keys, feature flags, tenant identifiers. Anything genuinely secret stays on a server that never hands it to a browser, exactly as before.
Does this work with Vite and Webpack?
Yes, and the change is the same shape in both, because it is not really a bundler change. You stop reading import.meta.env or process.env at module scope and start reading a value that was fetched. The bundler stops being involved the moment the value is no longer inlined.
The one thing that does stay at build time is the set of remote names in a Module Federation setup. Names are structural. The URLs behind them are not, and those can come from the same document as everything else.
What does this have to do with rollback?
Everything, and it is the same mechanism. Once an environment is described by a document rather than by a build, changing what an environment serves is a write, not a deploy. That is what makes a thirty-second rollback possible, and it is why the two are worth doing together: one build for every environment is the thing that makes the old artifacts still exist when you need them.
Build once, test that one, run it everywhere. The configuration is the only thing that was ever environment-specific — so it should be the only thing that varies.


