ArchitectureGENERALFRAMEWORK-SPECIFICPLATFORM-SPECIFIC

Micro Frontends

Independently owned and deployed frontend boundaries. The driver is almost always organisational rather than technical, the benefit lands on teams, and the cost lands on users.

The intent, the obvious build, and why it breaks

Every lesson starts where the work starts: someone wanted an outcome, and the first implementation that comes to mind has a problem.

The question

When does splitting a frontend into independently deployed units pay for itself, and who actually pays the bill?

The user intent

A person opens one page. They expect one product: consistent controls, one navigation model, one login, one set of keyboard behaviours. They have no idea, and no reason to care, that four teams built the panels in front of them.

The obvious build

Our teams keep blocking each other in one repository and one deploy. Split the frontend into independently deployable units, one per team, composed in a shell — the way the backend was split into services.

Why it breaks

The units each bring their own copy of the framework, the design system, the date library and the state library. The browser downloads, parses and keeps in memory one copy per unit, and the duplication compounds with every team added.

How it breaks in a real browser
  • The units each bring their own copy of the framework, the design system, the date library and the state library. The browser downloads, parses and keeps in memory one copy per unit, and the duplication compounds with every team added.
  • Composition is not free at run time: the shell must load a manifest, resolve each unit, mount it, and coordinate routing between units — work that a single build does at build time for nothing (The Module Graph).
  • Two teams upgrade the shared design system on different schedules, so two versions of the same button appear on one screen, with different focus rings and different heights.
  • Focus order, heading structure and landmark regions are properties of the composed page, and no team owns the composed page. A screen reader hears four main landmarks and a heading hierarchy that restarts three times.
  • A single unit throwing during mount takes out its region of the page. If the shell does not isolate failures, it takes out the page.
  • The organisational problem often survives the split intact: teams still coordinate, but now over a runtime contract with no compiler checking it (What a Component Owes Its Caller).
IntentEventStateUI LogicDOM WorkNetworkLayout / PaintPixelsFeedback

What is actually happening

In the browser, not in the framework.

  • A micro frontend is a deployment boundary drawn inside the frontend. Each unit has its own repository or package, its own build, its own release, and its own team.
  • Build-time composition links units at build time into one artifact. You get one bundle, deduplicated dependencies and type checking across the seam — and you lose the independent deploy, which was the entire point.
  • Run-time composition loads units in the browser: a shell fetches a manifest, imports each unit's entry, and mounts it into a region. This is the version that actually delivers independent deploys, and it is the version that costs users.
  • Server-side composition assembles HTML from several producers before it reaches the browser, which keeps the page a single document and moves the coordination cost onto the server (Server-Side Rendering).
  • Shared dependencies are the hard part. Either every unit bundles its own copy — simple, correct, and heavy — or units share a singleton at run time, which requires every unit to agree on a compatible version range and turns a dependency upgrade into a cross-team negotiation.
  • Whatever the mechanism, the browser still has one main thread, one DOM and one accessibility tree. Independence exists in the build and the org chart; it does not exist at run time (What the Main Thread Owns).

What this makes the browser do

And which of it is avoidable.

  • Downloading and parsing a framework runtime once per unit that does not share it. Parse and compile are main-thread CPU work, and it is duplicated work with no user-visible benefit (The Real Cost of JavaScript).
  • Resolving a manifest before any unit can be requested, which adds a serialised network hop in front of the units' own loading (Reading a Network Waterfall).
  • Mounting several independent application roots, each with its own reconciler, its own scheduling and its own listener set, all contending for the same thread.
  • Holding several component trees and several caches in memory simultaneously, with no shared eviction policy, for the life of the tab.
  • Style resolution across units that may ship overlapping CSS, which is where either strict scoping or the cascade decides your layout for you (The Cascade).

What is actually being split, and what is not

The picture below is the whole idea and the whole problem in one frame. Above the line, four teams own four builds and four deploys, and the independence is real. Below the line, all four land in one document, on one main thread, in one accessibility tree, sharing one URL. Nothing about the browser was split.

This is where the analogy to backend service decomposition stops being useful. Services are isolated by the operating system: separate processes, separate memory, separate failure domains, separate resource limits. Frontend units are isolated by convention only, and convention is not enforced by anything at run time (Microservices in Architecture).

Independent above, shared below
publishespublishespublishesresolved at run timemounts unitsTeam A repo + build + deployTeam B repo + build + deployTeam C repo + build + deployManifest / registry of unit URLsShell: routing, auth, layout, design system versionOne document, one URLOne main threadOne accessibility treeOne product, as far as the user is concerned
UserLLMAgentToolDataDecisionHumanGuardrail

The bundle problem compounds

A single build deduplicates by construction: one framework, one design system, one date library, whatever the number of features. Independently built units deduplicate only if something makes them, and the default — each unit bundling what it imports — means the cost of a shared dependency is multiplied by the number of units on the page.

It compounds in a second, worse way. Each team measures its own unit, sees a reasonable number, and ships. Nobody is measuring the composed page, so the growth is invisible until a user complains, at which point the cost is distributed across four backlogs and belongs to nobody. A page-level budget with a named owner is the only mechanism that catches this, and it has to exist before the split, not after.

Where a shared dependency lives
Each unit bundles what it imports
unit-a.js   framework (copy 1) + design system 4.2 + dates
unit-b.js   framework (copy 2) + design system 4.5 + dates
unit-c.js   framework (copy 3) + design system 3.9 + dates

-> three framework runtimes parsed on one page
-> three design system versions rendering side by side
-> each team's own bundle report looks fine
Shell provides pinned singletons
shell.js    framework (single copy) + design system 4.5
unit-a.js   feature code only
unit-b.js   feature code only
unit-c.js   feature code only

-> one runtime parsed, one design system version
-> a dependency bump is a coordinated release
-> the independence you gave up is now explicit

The duplicated version costs every user parse time and memory on every visit, and it produces visibly inconsistent controls. The singleton version costs the teams a coordination step on upgrades. Naming which of the two you have chosen is the point — the failure is having the first while believing you have the second.

The composed page is the accessibility contract

Every unit can be independently accessible and the composed page can still be unusable. Landmarks, heading order, focus order, announcement and the meaning of Escape are properties of the document, and the document is assembled from parts whose authors never saw each other's output.

The specification below is the shell's, not any unit's. It is worth writing down explicitly, because these are exactly the rules that a unit developed in isolation — as a full page in its own repository, with its own h1 and its own main — will violate the moment it is composed.

accessibility specComposed page assembled from independently owned unitsThe contract the shell owns and every unit must honour

semantics Exactly one main landmark, owned by the shell. Units render into it as section or region with an accessible name. One h1 per page, from the shell or the primary unit; units start at h2 and never hardcode a level they cannot know.

Tab / Shift+TabMoves through the composed page in visual order, across unit boundaries. Units must not reorder tab index globally or trap focus unless they own a modal.
EscapeDismisses the innermost dismissible thing. The shell defines the layering so two units cannot both claim it.
Skip linkShell-provided, moves focus to the primary content region rather than to whichever unit happens to load first.
Route change keys (browser back / forward)Handled by the shell's router; units request navigation rather than manipulating history themselves (History and Navigation).
Focus
  • The shell moves focus on route change, to the heading of the newly primary region — a unit must never grab focus on mount, because it does not know whether it is the reason the route changed.
  • Focus order follows DOM order; units are inserted in the order they are laid out, not the order they finish loading, so slots are reserved in the document before the units arrive.
  • When a unit fails to load, the shell renders an error region that is focusable and named, rather than leaving a silent gap in the tab order.
Announces
  • One shell-owned polite live region for route and status announcements. Units publish messages to it rather than creating their own (Live Regions and Announcement).
  • A unit that finishes loading late announces its arrival only if it changed what the user is currently doing — arrival announcements from four units at once are noise.

usually broken by Each unit is built and tested as a standalone page, so each ships its own h1, its own main, its own live region and sometimes its own focus trap. Every unit passes its own accessibility tests; the composed page has three h1 elements, four landmarks with the same name, and interleaved announcements — and no team's test suite ever renders the page that users actually get.

How to build it

Most important first.

  • First, prove the coupling. Measure how often a team is actually blocked by another team's release, and for how long. If nobody can name the last time it happened, the architecture is solving an imagined problem and its costs are real (Modular Monolith in Architecture).
  • Try boundaries inside one build first: enforced module ownership, code ownership rules, independent test suites, and per-area deploy verification. Most of the autonomy, none of the runtime cost.
  • If you do split, split along genuine product seams — whole pages or whole routes owned end to end — not by widget. Several units on one screen is where duplication, inconsistency and accessibility fragmentation all peak.
  • Make the shell own everything that is page-global: routing, authentication, the design system version, layout, landmarks, heading level policy, error boundaries, and the announcement channel for route changes.
  • Pin the shared runtime and the design system as shell-provided singletons with an explicit compatibility policy, and treat a version bump as a coordinated release. Pretending versions are independent is how two buttons appear (Design Systems).
  • Budget the composed page, not the units. Every team optimising its own unit while the composed page grows is the characteristic failure, and only a page-level budget catches it (Bundle Analysis).
  • Isolate failure explicitly: a unit that fails to load or throws on mount degrades its region and leaves the rest of the page usable.

Keyboard, focus, semantics, announcement

A required field on every lesson in this domain, not a section added when there is room.

  • The accessibility tree is built from the composed document, not from any unit. Landmarks, heading order and focus order are page-level properties that emerge from four teams' independent decisions, which is why they are the first thing to break (The Accessibility Tree).
  • Focus movement across a unit boundary is nobody's test case. A user tabs out of one team's region and into another's, and the two have different focus-visible styles, different tab-order conventions and sometimes a focus trap that was written assuming it owned the page (Keyboard Operability).
  • Announcement needs a single owner. Two units each declaring their own live region means messages interleave unpredictably, or one silently overrides the other (Live Regions and Announcement).
  • Heading level is a composition concern: a unit that hardcodes an h1 because it was a page in its own repository produces three h1 elements once it is a panel (Document Structure and Reading Order).
  • This is the strongest single argument for a shell-owned design system: if the button, the dialog and the field are provided once and correctly, most of the accessibility surface is right by construction regardless of which team assembled the screen (Design Systems).

What can go wrong

Failure modes
  • Bundle-size compounding: each unit is reasonable, the page is not, and no single team's dashboard shows the problem because every dashboard is scoped to one unit.
  • Version skew between units on the same page — two design system versions, two state library versions, occasionally two framework majors, all live at once (Long-Lived Clients and Version Skew).
  • A shell that has quietly become a distributed monolith: every unit needs a shell change to ship, and the coordination is worse than the single build it replaced.
  • Cross-unit communication over a custom event bus with no schema, so a rename in one team breaks another team silently at run time and only in production.
  • Inconsistent interaction models — two date pickers, two modal implementations, two focus-trap behaviours — which reads to users as an unreliable product rather than as four teams.
  • The mitigation failing: shared singletons that force every unit onto one version, which reintroduces exactly the lockstep release the split was meant to remove.
What can arrive out of order
  • Units finish loading in an order determined by the network, so any interaction that depends on two units being mounted must express the dependency rather than assume load order.
  • Shared singleton initialisation races: two units request the shared runtime nearly simultaneously and, without a resolution mechanism in the shell, both can initialise their own copy.
  • A user navigates between units while one is still mounting, so a mount completes into a region the shell has already replaced.
Security
  • Every unit composed at run time executes with the full authority of the page. There is no partial trust for a script tag, and a compromised build pipeline in one team's repository is a compromise of the whole product (Third-Party Scripts and the Supply Chain).
  • A Content-Security-Policy must cover every origin any unit might load from, and each additional origin widens what an injection can reach (Content Security Policy).
  • Authentication belongs to the shell. Units that each obtain their own tokens multiply the places a credential can be stored badly, and multiply the refresh races (What the Frontend Is Responsible For in Auth).
  • A custom cross-unit message bus is an internal trust boundary that usually has no validation at all, because everyone assumes the other side is first-party (Parse, Validate, Authorize, Process in Security).
Misreads
  • "Micro frontends are the frontend equivalent of microservices." Services run in separate processes with separate resources and fail independently. Frontend units run in one document, on one thread, sharing one DOM and one accessibility tree. The isolation that makes the backend pattern work does not exist here (Microservices in Architecture).
  • "They make the frontend faster." They make deploys more independent. On the page they add bytes, parsing and a composition step, and the honest framing is that a team benefit is being paid for by users.
  • "Independent deploys mean independent failures." A unit that throws on mount can break the shared page unless the shell was explicitly built to isolate it, and most shells are not, at first.
  • "We need this because we have many teams." Many teams is a necessary condition, not a sufficient one. The sufficient condition is measured, sustained deploy coupling that module boundaries inside one build could not fix.
  • "The shared design system will keep it consistent." Only if versions are pinned by the shell. Left to teams, the design system becomes a set of similar-looking components at different versions (Design Tokens).

Measuring it, and what changes in the field

How you would see this
  • Measure the composed page, on a cold cache, as a user experiences it: total transferred bytes, duplicated modules, and main-thread time before the page is usable (Bundle Analysis).
  • Coverage tooling shows how much of each unit's shipped JavaScript actually executed on a given screen; duplicated framework copies show up clearly as parsed-but-idle bytes.
  • The organisational side needs measurement too: releases per team per week, and how many of them required another team. That number is the benefit being claimed, and it is testable.
  • A design-consistency audit — count the distinct button, input and modal implementations rendering on one page — quantifies the drift that users describe as "it feels janky" (Visual Regression Testing).
Slow device, slow network, large data, old tab
  • On a slow device, duplicated framework parsing is the dominant cost and it grows linearly with the number of independently bundled units on the page.
  • On a slow network, the manifest-then-units waterfall adds serialised round trips that a single bundle does not have (The Critical Rendering Path).
  • With two teams the coordination saving is negligible and the runtime cost is already real. With twenty teams and genuinely separate products, the arithmetic can invert.
  • In a long-lived tab, units that were deployed at different times drift apart in their assumptions about the API and about each other (Deploying a Frontend).
What this costs
  • Team autonomy is bought with user-visible cost: duplicated bytes, duplicated parsing, composition work at run time, and a page that is only as consistent as the weakest shared contract. That trade is sometimes right; it is never free, and it should be stated in those terms.
  • Strict shared-dependency policies restore consistency and reduce duplication, and take back a large part of the independence that motivated the split.
  • Server-side composition keeps the page a single document and preserves most of the accessibility properties, at the cost of moving the coordination problem into a server that must now aggregate several producers (Composed APIs: Aggregating Other Services in API Design).

Where this applies

Frontend advice ages badly and fragments across engines. These labels say what each claim is specific to, and where a different browser, device or framework would differ.

  • GENERALThe structural costs — duplicated dependencies, run-time composition, page-global concerns without a page-global owner — follow from the browser having one document, one main thread and one accessibility tree, so they apply to every implementation regardless of the bundler or composition tool used.
  • FRAMEWORK-SPECIFICModule Federation in webpack and Rspack can share a singleton runtime between units, which removes much of the duplication but requires compatible version ranges across teams; import-map and iframe based composition make different trade-offs, with iframes buying real isolation at the cost of a fragmented accessibility tree and awkward layout.
  • PLATFORM-SPECIFICScreen readers differ in how they present multiple application roots and duplicated landmarks within one document, so a composition that reads acceptably in one assistive technology can be genuinely confusing in another — this needs testing with the technologies your users actually run, not one.

Where the depth lives

This domain teaches the browser-side mechanism and hands the rest off.

Domains that do not exist yet
  • Software Design — the coupling and cohesion analysis that tells you whether a boundary is real. A boundary that needs constant cross-team negotiation was drawn in the wrong place, and moving it into separate deploys makes it harder to move again.
  • Distributed Systems — run-time composition makes the page a distributed system: partial failure, version skew and no atomic update across units, all inside one document.