A marketing site that grows into a platform eventually faces a scaling question that has nothing to do with traffic: how do several teams ship front-end features without stepping on each other? Branch contention, shared build times, and one team's bug blocking everyone's release are organizational problems that show up as front-end architecture. Micro-frontends are our answer, and Blazor makes them surprisingly clean to host.
The shell-and-remotes model
The pattern is a thin host shell plus a set of remotes. The shell owns the things that must be global - layout, navigation, theme tokens, authentication context - and nothing else. Each remote is an independently owned slice of UI that the shell loads into a region. The contract between them is deliberately small: the shell tells a remote where to render and passes a typed context; the remote renders itself and reports back through events.
flowchart LR
shell[Host Shell] -->|context| remoteA[Remote: Pricing]
shell -->|context| remoteB[Remote: Blog]
shell -->|context| remoteC[Remote: Integrations]
remoteA -->|events| shell
remoteB -->|events| shell
The key discipline is that remotes never reach into each other. If the pricing remote needs something the blog remote knows, that flows through the shell or a shared service, never through a direct dependency.
Configuring the regions
The shell needs to know which remotes exist and where they mount. We drive that from options rather than code, so adding a remote is a configuration change, not a recompile of the shell:
{
"MicroFrontends": {
"Remotes": [
{ "Id": "pricing", "Region": "main", "Enabled": true },
{ "Id": "blog", "Region": "main", "Enabled": true }
]
}
}
Binding those options to a strongly typed class keeps validation in one place and lets a remote be feature-flagged off without a deploy.
Isolation is the whole point
The reason to do this at all is failure isolation and independent delivery. Two rules protect both:
- A remote that throws must not take down the shell. Render each region inside an error boundary so a broken remote degrades to a placeholder instead of a blank page.
- A remote owns its own assets. Scoped CSS and namespaced scripts keep one team's stylesheet from leaking into another's layout.
Note: The moment a remote depends on another remote's internal markup or styles, you have a distributed monolith with extra steps. Guard the boundary in code review.
Sharing without coupling
Some things genuinely are shared - the design tokens, the current user, the culture. Those belong to the shell and are passed down as a read-only context, not duplicated in each remote. Cross-cutting actions, like a remote asking the shell to navigate, go through a small mediator the shell exposes. This keeps the shared surface tiny and explicit, which is what lets the remotes evolve independently.
When micro-frontends are the wrong call
This architecture earns its keep when multiple teams need independent delivery. For a single team on a single product, it is overhead - you pay the coordination cost of the contract without the organizational benefit. Reach for it when team boundaries, not page counts, start to hurt.
Wrapping up
Micro-frontends in Blazor come down to a disciplined contract: a shell that owns the global concerns, remotes that own their slices, and a small, explicit surface between them. Get the isolation right - error boundaries, scoped assets, no cross-remote reach - and you trade a one-time architectural cost for the ability to let teams ship on their own schedule.
Planning a platform that several teams will build at once? Talk to our architects.
