Error isolation and resilience in micro-frontends
A micro-frontend platform has more moving parts than a single app, so sooner or later something will fail to load. The goal is that one broken area never takes the whole product down.
Isolation is something you build
Micro-frontends are often sold with the promise that a failure in one area stays in that area. That only holds if you design for it. By default, remotes loaded with Module Federation or Native Federation run in the same document, on the same JavaScript thread, with the same global objects. An unhandled error during startup can leave the user with a blank page, exactly as in a monolith.
The ways a remote can fail
| Failure | Typical cause | What the user sees without protection |
|---|---|---|
| Remote entry does not load | Network error, bad deploy, CDN outage | Blank area or a navigation that never finishes |
| Remote loads very slowly | Large bundle, slow network | A spinner with no end |
| Chunk missing after a deploy | An old tab asks for files that were deleted | Error on the next navigation |
| Remote throws while starting | Bug, shared dependency mismatch | Blank area, sometimes a broken shell |
| Remote throws while running | Bug in a screen | Frozen or half-rendered screen |
Each one needs a different defense.
Load failures: catch, retry, fall back
Loading a remote is a dynamic import, and a dynamic import returns a promise that can reject. The host should treat that rejection as a normal case.
- Catch the error where the remote is loaded. In an Angular host this is usually the
loadChildrenorloadComponentfunction of the route. Add acatchthat returns a fallback route or component. - Retry once. Many failures are temporary network problems. More than one retry usually only makes the user wait.
- Show a useful fallback. Say that this area is unavailable, offer a retry button and keep the navigation working. The rest of the product must stay usable.
The manifest that lists the remotes deserves the same care. If the host fetches it at startup and the request fails, nothing loads. Bundle a last-known-good copy with the host, or serve the manifest from the same origin and cache as the host itself.
Timeouts
A dynamic import has no timeout. A request that hangs keeps the user in front of a spinner. Wrap the load in a race against a timer, and show the fallback when the timer wins. Count every timeout as a load failure in your metrics.
Stale chunks after a deploy
A user who opened the app yesterday still holds references to yesterday's files. If a deploy deletes them, the next lazy load fails. Two habits avoid this:
- Keep previous versions of each remote on the CDN for a period after each release. Files with a content hash in the name can live side by side.
- When a chunk fails to load, fetch the manifest again and retry before giving up.
Runtime errors: a boundary per area
React has error boundaries. Angular has no direct equivalent, so the boundary is built from other pieces:
- A custom
ErrorHandlerin each remote that reports the error with the remote's name and version, and tells the host that this area failed. - A wrapper in the host around each mounted area. When an area reports a fatal error, the host replaces its content with the fallback and leaves the shell untouched.
- Guarded mounting. If remotes are mounted as custom elements or through a mount function, wrap the call in a
try/catch. A remote that throws while starting should never stop the host's own startup.
The shell must not depend on any remote to start. If the navigation, the session or the layout imports code from a remote, a failure in that remote becomes a failure of everything. More in the micro-frontend host shell.
Monitoring per remote
You cannot fix what you cannot attribute. Every error and every metric should carry the name and version of the remote that produced it.
- Tag errors with remote name, remote version and host version.
- Count load failures and timeouts per remote. A rise right after a deploy points to that release.
- Route alerts to the owning team. An alert that goes to everyone is read by nobody.
- Check remote entries from outside. A synthetic check that requests each remote entry URL every few minutes finds an outage before users do.
Fast rollback
When a release breaks an area, the fastest fix is to point the manifest back to the previous version. That only works if the previous files still exist and the manifest can change without rebuilding the host. Both are pipeline decisions, described in CI/CD for micro-frontends.
Test the failure paths. Block one remote entry in the browser's network tools and watch what happens. If the shell survives, the fallback appears and an alert reaches the right team, your isolation is real. See testing micro-frontends.
More in this section
- Micro-frontend performance: the costs and how to control them
- Testing micro-frontends: a practical strategy
- Monorepo vs polyrepo for micro-frontends
- From frontend monolith to micro-frontends, step by step
Planning a migration or a micro-frontend platform?
In a 20-minute call you describe your situation and I tell you how I'd approach it. If it isn't worth doing yet, I'll say so.
Message me on LinkedIn