Blog post

Micro Frontends: One Shell, Many Independently Deployable Remotes

Microservices solved this on the backend. In the browser, the same deployment autonomy is called micro frontends.

Dark cover card: headline “Micro Frontend” with React, Angular and Vue bundles composing one shell, one shell / many deployable remotes

Say you and I are on the same team. The two-year-old clean frontend app is now a giant monolith: the team has grown from thirty to forty, the repo holds hundreds of thousands of lines, and one npm run build takes 35 minutes. One team’s flaky e2e tests hold everyone else’s green PRs hostage in the pipeline. The next day you change just one button label, and the whole SPA rebuilds and redeploys. Because the build graph is tightly coupled, a one-line commit’s blast radius is the entire app.

On the backend, this problem is what microservices solve. In the browser, bringing the same deployment autonomy is called Micro Frontend (MFE).

Consider the dashboard of a customer-support SaaS. Four separate applications stand behind it:

Inbox: real-time conversations, threading, optimistic updates.

Workflow Builder: drag-and-drop canvas, node graph, condition editor (a different rendering model).

Knowledge Base: rich text editor, versioning, media.

Analytics: heavy charting, data viz, a different performance profile.

Each slice is a full product in itself, not a widget on a page. Every team’s domain depth is different. So the automation team ships their canvas ten times a day without touching the inbox’s real-time bundle. The user sees one dashboard, but behind it, several independently deployable app runtimes compose inside the shell at runtime.

What changes technically

Repo and ownership isolation: each slice gets its own repo, build, and deploy URL. The days of many hands on one giant webpack/vite config end, and merge-conflict contention drops.

Independent deployability: at runtime the shell just fetches the new remoteEntry.js. A new feature goes live without invalidating anyone’s bundle.

Fault isolation: if one remote crashes, the shell contains it with an error boundary. The whole app does not go down (graceful degradation).

Framework heterogeneity and incremental migration: the canvas on React, analytics on another charting stack, a legacy part on old Angular (they coexist).

Shared code: the design system, design tokens, and the auth widget ship as one shared package, and every remote consumes them as the single source of truth.

How does composition happen?

Nowadays the most modern approach is Native Federation. It takes Module Federation’s idea and stands it on web standards (ES modules and import maps) without being stuck in a bundler’s internals:

Build time: each remote emits a remoteEntry.json listing its exposed modules and the version constraints of its shared dependencies.

Runtime: the host reads it, negotiates dependencies into an import map, then pulls the remote with the browser’s native dynamic import().

Who it is not for

MFE is the answer to an organizational scaling problem, not a UI rendering problem. If the problem is “many teams, one bundle, one pipeline in contention”, then it fits. Otherwise, orchestrating ten remotes at runtime (that accidental complexity) will make your app heavier still.

Tags

Micro FrontendFrontend ArchitectureSystem DesignWeb DevelopmentSoftware Engineering