A shell application loads three remote modules at runtime. Two were built against one major version of the UI framework and one against the next. Everything works in the shell's local development setup and breaks in production with errors about invalid hook calls and missing context. What is happening, and how should the platform prevent it?
Show the full answer Hide the answer
Second by second, what happens
The shell loads remote A, which needs the framework. The container negotiation finds a compatible shared copy already loaded and reuses it. Then remote C loads, needs a different major version, and either gets the incompatible shared copy — producing the invalid-hook-call and missing-context errors, because component state and context are held in module-level variables inside that copy — or loads its own second copy, in which case both copies work internally and share nothing: two React trees, two context providers, and a component from one that cannot read a context from the other.
In local development it worked because only one remote was running, so there was only ever one version.
Where it amplifies
- The break is a deployment composition, not a build. Every remote's own CI is green. The invalid combination only exists once the shell loads them together, which happens in production first if nothing tests the assembled application.
- It is time-dependent. Load order and cache state decide which copy wins, so the failure is intermittent and varies between users.
- Shared state multiplies with copies. Routing, theming, internationalisation and analytics all typically depend on a singleton, so a duplicated framework quietly duplicates all of them: two routers fighting over the URL is the second-order symptom.
What stops it
- Declare the framework and every other singleton as a strict shared dependency, and fail loudly. Marking a dependency as a singleton with a required version range makes the mismatch an error at load time with a legible message, instead of a hook error 40 frames deep. A loud failure at composition is worth more than a subtle one at runtime.
- Own the version policy at the platform, not per remote. Publish a supported range, and treat a framework major version as a coordinated upgrade across the estate with a stated window — typically one quarter, with a deadline. Runtime composition means you have chosen a shared runtime, which means you have chosen coordinated upgrades; teams that wanted fully independent release cycles wanted separate applications, and the honest answer may be to give them that.
- Test the composition in CI. A pipeline job that loads the shell with the current production version of every remote and runs a smoke journey. This is the missing gate, and it is the one that makes runtime composition safe.
- Version the contract, not just the code. Remotes should expose a declared interface and a compatibility version the shell checks before mounting, so an incompatible remote is skipped with a fallback rather than crashing the page.
- Keep the singleton list short and explicit. Framework, router, design system, analytics. Every additional shared singleton is another coordination obligation, and a library that does not need to be shared should be bundled per remote even at the cost of bytes.
What would have to be true for independent upgrades to be safe
No shared singletons at all, which in practice means isolation at a boundary the browser enforces: separate pages with real navigation, or iframes. Both are unfashionable and both genuinely deliver the independence micro-frontends promise. Module federation buys a composition that looks like one application and pays for it with a shared runtime; choose it knowing that is the trade.
When this is the wrong architecture
If four teams share one application and deploy weekly, the coordination cost of runtime composition exceeds the coupling it removes. A monorepo with a build-time-composed application and a merge queue gives independent development with none of this failure class, and the deployment coupling it reintroduces is usually the smaller problem. The case for federation is a large estate where a full rebuild is genuinely too slow or where teams ship on different cadences — and even then, it needs the composition pipeline in item 3 before the first remote goes live.