A remote that hadn't changed started crashing in the test environment. The console said:
ReferenceError: Cannot access '_t' before initialization
at Module.OrderListComponent (1429.js:1:604)
_t is a minified name, 1429.js is a chunk, and the component in the stack trace hadn't changed. Every CI pipeline was green. Every build passed. The app only broke when the shell loaded it next to a brand-new remote from another team.
It took the better part of a day to find the real error underneath:
Error: [ Federation Runtime ]: Version 19.2.3 from inventory of shared singleton
module @angular/material/autocomplete does not satisfy the requirement of orders
which needs 19.2.1
(Names and versions changed. The shape of the bug is real.)
What actually happened
We run an Angular micro-frontend platform on Module Federation. A shell loads several remotes, each built and deployed from its own repo by its own team. Big libraries like Angular, RxJS and the UI kit are shared as singletons: only one copy is loaded, and every remote uses it.
Two remotes were involved:
| Remote | package.json |
Version it ships |
|---|---|---|
orders (old) |
"@angular/material": "19.2.1" |
19.2.1 |
inventory (new) |
"@angular/material": "^19.2.0" |
19.2.3 |
Here's how the federation runtime resolves a shared singleton, using the default strategy:
- Every loaded participant offers the version it was built with.
-
The highest version wins. Here that's 19.2.3, from
inventory. - Every participant checks the winner against its own
requiredVersion. -
orderspinned the exact version19.2.1, so19.2.3fails its check.
With strictVersion the runtime throws the error above. Without it, orders silently runs on a version it was never built or tested against. When that happens halfway through loading a module graph, you get errors like Cannot access '_t' before initialization, which point nowhere near the real cause.
Why nothing caught it
Every check we had looked at one repo at a time:
-
ordersbuilt fine with 19.2.1. -
inventorybuilt fine with 19.2.3. - Unit tests, lint and e2e tests all passed per app.
The conflict only exists when the two remotes are combined at runtime in the browser, and no step in either pipeline ever looked at that combination.
The fix took one character:
- "@angular/material": "19.2.1",
+ "@angular/material": "^19.2.1",
Rebuild orders, redeploy, done. The day lost to debugging was the expensive part, and a bug that only shows up when two teams' deploys meet will come back in some other form.
The data was there all along
@module-federation/enhanced (used by Nx, rspack, Rsbuild and Modern.js) writes an mf-manifest.json next to every remoteEntry.js. It lists every shared package with:
- the
versionthat remote ships - the
requiredVersionit will accept - whether it's a
singleton
That's everything needed to predict the crash before it happens. You don't need source access to other teams' repos, only the URLs of their deployed manifests.
So I built mfe-guard
mfe-guard is a small CLI that fetches the manifests of your shell and remotes, works out which version of each singleton will actually win at runtime, and fails when any participant won't accept it.
npx mfe-guard check \
-m shell=https://app.test.example.com/mf-manifest.json \
-m orders=https://orders.test.example.com/mf-manifest.json \
-m inventory=https://inventory.test.example.com/mf-manifest.json
What it reports:
-
error: a singleton's winning version doesn't satisfy someone's
requiredVersion(the bug above), with the exact change to make - error: different major versions of a shared package, or two remotes using the same federation name (they overwrite each other's modules)
- warn: singletons pinned to an exact version, a crash waiting for the next patch release
-
warn:
@angular/core,rxjs,zone.jsor React shared withoutsingleton: true -
info: compatible drift, for example "
orderswas built on ui-kit 2.13 but will run on 2.17 at runtime". It's not an error, but you probably want to test it.
The first time I ran it against our real test environment, it flagged exactly that kind of minor drift, something nobody had noticed.
Put it in CI
Add a config file to the shell repo:
{
"participants": [
{ "name": "shell", "manifest": "https://app.test.example.com/mf-manifest.json" },
{ "name": "orders", "manifest": "https://orders.test.example.com/mf-manifest.json" },
{ "name": "inventory", "manifest": "https://inventory.test.example.com/mf-manifest.json" }
]
}
Then add one step after a remote deploys to the test environment:
- run: npx mfe-guard check
The command exits with code 1 on any error, so the pipeline stops before the combination reaches production. There's also a project mode (-p name=./path) that reads package.json and node_modules when you don't have manifests, and --json for other tooling.
What I'd tell my past self
- Don't pin shared singletons to exact versions. With singletons the highest version wins, so an exact pin will eventually break when someone else ships a patch.
- Check the deployed combination, not just each repo. In micro-frontends the risk sits between the repos.
- Keep one list of shared versions across teams, or at least look at the drift regularly. Patch drift is noise; minor drift is a test you haven't run.
mfe-guard is open source (MIT) and young: v0.1.1, built because of this exact bug. If you run Module Federation in production, I'd love to know whether it catches anything in your setup, or what it misses.
- npm: mfe-guard
- GitHub: maneeq/mfe-guard
Have you hit a Module Federation version bug that only showed up at runtime? I'd like to hear how you tracked it down.

Top comments (0)