Orchestration is a different problem from loading
single-spa routes and mounts applications; Module Federation loads modules — teams reach for one when they needed the other, and an imperative unmount hook is a promise you make rather than one the framework keeps for you.
A platform engineer at the host has read about micro-frontends over the weekend and opens the conversation with “we were thinking single-spa for this.” You are delivering one component into one page. Somewhere in the next twenty minutes you have to work out whether they are offering you a seam or proposing that their application be rebuilt around your widget.
The two get confused constantly, in both directions, and the confusion is expensive in a specific way: it produces architecture that is correct for a problem nobody in the room has.
Two jobs that sound like one
Loading answers: where does this code come from, and which copy of its dependencies does it run against. That is the Module-Federation lesson’s subject, and it is a build-time and fetch-time question.
Orchestration answers: which application owns the screen right now, what happens on the way in, and what happens on the way out. That is a lifecycle and routing question, and no amount of clever fetching addresses it.
single-spa is explicit about being the second one. Its own overview describes a root config that registers applications, each with a name, a load function and an activity function, and each implementing bootstrap, mount and unmount (plus an optional update). The stated payoff is running “multiple frameworks on the same page without page refreshing”. Note what the activity function is for: it is a predicate over the current URL. The root config is a router whose routes resolve to whole applications instead of to views.
A registered application still has to come from somewhere, and the load function is where that happens — which is exactly the seam where Module Federation, or a dynamic import, or a script URL, plugs in. The two mechanisms are not alternatives. One decides when your application is on screen; the other decides where its code and dependencies come from.
Where people get burned
Both of these projects document their own products, and both are selling an architecture rather than a component. Their documentation is the right place to learn what the pieces do and the wrong place to learn whether your host needs the pieces at all. Neither site has a page arguing that you should not adopt it.
What the lifecycle makes you responsible for
The lifecycle contract is four exported functions, and the shape is plain:
export const bootstrap = async () => {}
export const mount = async (props) => { /* attach listeners, subscriptions */ }
export const unmount = async (props) => { /* tear down everything mount() created */ }Here is the part worth being careful about, and it is this course’s own observation rather than something single-spa states about itself. An unmount hook is a promise you make, not a promise the framework keeps for you. Any lifecycle expressed as imperative paired hooks has the same property: whatever mount attached to something that outlives the component — a listener on window, an interval, a ResizeObserver, a store subscription, a websocket, a pending fetch — keeps running until your own code removes it. The framework unmounts your application. It does not know what your application did.
Label that correctly when you repeat it. I looked through single-spa’s documentation for guidance on cleanup and memory specifically, including its ecosystem page, and found none — that page lists framework helper packages and nothing about teardown. So the leak argument above is an inference about imperative lifecycle contracts in general, and it is offered here as the course’s reasoning. What makes it worth teaching anyway is the symptom: nothing goes wrong on the first mount. It goes wrong on the fourth route change, in a session an hour long, on somebody else’s machine.
What adopting an orchestrator actually asks of the host
Read the overview’s own description back as a list of changes, because the vocabulary hides how large they are. This is the course’s reading of what the architecture implies, not a migration guide the project publishes.
- A new top-level entry point. The root config becomes the thing the browser boots. Whatever boots the application today stops being the entry point and becomes an application that gets registered.
- Routing moves up a level. Activity functions decide which application is active for a URL, so the host’s existing router now runs underneath a router it does not own. Every route-adjacent behaviour they have — guards, redirects, scroll restoration, analytics on navigation — has to be placed on one side of that line or the other.
- Every application implements the lifecycle.
bootstrap,mountandunmountare exports each registered application has to provide, which means their existing application acquires a teardown obligation it has never had, because until now it was never unmounted.
That last one is the sharp edge, and it is worth saying in the meeting. An application that has only ever been loaded and then discarded by a page navigation has never had to clean up after itself. The first time it is asked to unmount, every listener it has ever attached to window is discovered at once.
Where this course lands on it
Orchestration is not worth its cost for the job this course is about, and that is a verdict rather than a finding. One agentic surface dropped into a host that already routes its own pages does not need a router of routers. You would be adding a boot sequence, a registration file and a lifecycle contract to solve a placement problem that a custom element solves with a tag.
The case where it earns its keep is real and specific: the host page is genuinely composed of several independently deployed applications, in more than one framework, whose presence on screen is decided by the URL. If that is true of your host, an orchestrator is not overhead, it is the thing already doing the coordination, and registering as one more application is the low-friction path. Ask one question to find out: does something in this codebase already decide which application is mounted, based on the route? If the answer is no, you are being offered a rewrite, not a seam.
Check your recall
Answer from memory — no scrolling back.
Retrieval check
A host team offers you single-spa as the delivery mechanism. Name the one question that tells you whether that is a seam or a rewrite.
Check your answer
Does anything in this codebase already decide, from the route, which application is mounted? If yes, the root config exists, registering one more application is a documented and low-friction path, and the load function is where your bundle arrives. If no, they are proposing to restructure how their product boots so that your one surface can be placed, and that is a rewrite wearing the vocabulary of an integration.
The follow-up that settles the meeting: ask what happens to their current router. An orchestrator has to own top-level routing, so if nobody has an answer to that, nobody has thought the proposal through yet, and you can say so without saying no.
Hands on
Write the teardown inventory before you need it
Done when: ARTIFACT.md carries a teardown inventory for the review gate: every subscription, listener, timer and observer it creates, each paired with the exact line that removes it, and at least one entry proven by watching the count in DevTools rather than by reading the code.
- List everything your component attaches that outlives its own DOM node. Work from the object rather than from memory: what does it add to
window, todocument, to anAbortController, to a store, to aResizeObserverorMutationObserver, tosetInterval, to an open stream. - Beside each one, write the exact teardown call. If any line is blank, that is the leak, and you have found it before a user did.
- Prove one of them. Mount and unmount the component ten times in a row — a button in a scratch page is enough — and watch a count that should return to its starting value. Listener counts in the Elements panel, or your own counter incremented in
mountand decremented inunmount, both work. - Deliberately comment out one teardown call and repeat the ten cycles. You want to have seen the number climb, so that the shape of the symptom is familiar the day it appears in somebody else’s application.
- Copy the finished inventory into
learning/grafting-ui/ARTIFACT.md, and add one line to the graft contract you wrote in the contract lesson: what your component guarantees to release on removal. This is a promise the host team can hold you to, which is the point.
What this does not cover
Every mechanism in this module so far assumes your code gets to run in the host’s document, sharing its globals, its cascade and its origin. The lesson on the iframe takes the opposite bargain: a real boundary, a separate document, and a bill for it in sizing, session and every message having to survive an origin check you write yourself.
And whether any of the three survives contact with production is decided before you write a line, by headers neither build system can see. The lesson on how CSP narrows the table closes this module by putting the directives against the mechanisms, including the one directive that breaks a component’s styling rather than its loading.
Read this next — primary source
single-spa — getting started overviewsingle-spa.js.org — fetched 2026-09-05. The single-spa project documenting its own product; it is describing an architecture it wants you to adopt.
Read it for one thing: the shape of a root config, and the fact that everything it does is about deciding which application is active right now. That framing is what separates orchestration from loading, and it is stated more clearly here than in any summary of it. Read past the first section too, because the lifecycle contract — bootstrap, mount, unmount, and the optional update — is where every obligation this lesson warns about actually lives. It is the project writing about itself, so take the architecture as described and the case for adopting it as advocacy.
Stuck, curious, or think this lesson is wrong? Ask your teaching agent. The lessons are the scaffold; the conversation is where the learning gets unstuck.