single-spa vs Module Federation: how to choose
These two tools are often compared as if they did the same job. They overlap, but they start from different places: one manages application lifecycles, the other loads code and shares dependencies.
Two different jobs
single-spa is an orchestrator. It is a small library that decides which applications are active for the current URL, and mounts and unmounts them.
Module Federation is a loading and sharing mechanism. It lets one build consume modules from another build at runtime and share dependencies between them.
How single-spa works
A single-spa platform has a root config: one HTML page and a script that registers the applications.
registerApplication({
name: 'billing',
app: () => import('billing'),
activeWhen: '/billing'
});
start();
Each registered application exports three lifecycle functions: bootstrap, mount and unmount. Helper packages such as single-spa-angular, single-spa-react and single-spa-vue generate them for each framework.
When the URL changes, single-spa unmounts the applications that are no longer active and mounts the ones that are. Several applications can be active at once, for example a navigation bar and the page below it.
single-spa does not prescribe how code is loaded. Its documentation has long recommended import maps, loaded through SystemJS or, in current browsers, natively. In the example above, the name billing is resolved to a URL by a native import map.
How a Module Federation platform works
The host is a regular application in one framework, for example Angular. Its router lazy-loads routes from the remotes. There is no separate lifecycle. The remote's routes and components become part of the host's running application: one framework instance, one router, one dependency injection tree.
The same model applies to Native Federation, compared in Module Federation vs Native Federation.
Side by side
| single-spa | Module Federation | |
|---|---|---|
| What it is | Top-level router and lifecycle manager | Runtime module loading and dependency sharing |
| Unit of composition | A whole application that mounts and unmounts | A module: routes, a component, a service |
| Several frameworks | Designed for it | Possible, with a wrapper per framework |
| Routing | single-spa picks the application; each one routes inside its prefix | The host router owns the URL; remotes add child routes |
| Shared dependencies | Through import maps and externals, managed by you | Built in, with version rules |
| Shared services | Separate application instances; share through utility modules | One framework instance; singleton services work as usual |
| Angular specifics | Each application bootstraps its own Angular app; zone.js should load once for the page | Remotes feel like lazy-loaded features |
Multi-framework support
This is the clearest difference.
single-spa treats every application as a black box with three functions. An AngularJS app, a React app and an Angular app can sit side by side, each with its own framework instance. There is even a single-spa-angularjs helper, which makes it a common choice during legacy migrations.
Module Federation assumes that host and remote can share a runtime. With one framework that is a strength. With several, the remote in another framework has to be wrapped, usually as a custom element, and the host loads it as a plain script or module. That works, but the lifecycle handling that single-spa gives you has to be written by hand.
Trade-offs
single-spa
- Clean separation between application instances. One application rarely breaks another's internals.
- Each application bootstraps its own framework instance. That costs startup time and memory, even when the framework code itself is shared.
- Sharing a service between applications is manual: a shared utility module, events or the URL.
Module Federation
- For a single-framework platform, it gives the simplest developer experience. A remote is developed almost like a lazy-loaded feature.
- Tight version alignment is required for the framework. One misconfigured shared dependency can affect every remote.
- Less isolation. Remotes run inside the host's application, so a failing remote needs explicit containment. See Error isolation and resilience.
When to choose each
- All teams on Angular, with versions aligned by policy: federation (Module or Native). It is the lower-cost option to run.
- Different frameworks for the long term, or products acquired with their own stacks: single-spa, or federation with custom element wrappers if only one or two parts differ.
- AngularJS next to modern Angular during a migration: both work. single-spa has dedicated helpers. With federation, the legacy app is wrapped and loaded as one more remote, as described in Running a legacy app inside a micro-frontend host.
- Both together: single-spa for lifecycles, federation for loading and sharing. This is a valid combination, and it means two tools to understand and debug. Choose it only when you need both jobs done.
Decide on the number of frameworks first. If the honest long-term answer is "one", federation alone is enough. If it is "several, and that will not change", an orchestrator earns its place.
More in this section
- Iframes, web components, packages or federation: integration techniques compared
- The micro-frontend host: what it is responsible for and what it must not do
- Routing across micro-frontends: route ownership, deep links and the navigation contract
- Sharing login and session across micro-frontends
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