AniUI Academy

Module Federation and Composition Models

How independently-built micro-frontends actually get combined into one page — module federation conceptually, and the build-time versus run-time composition trade-off.

12 min read

Splitting a frontend into independently deployable pieces (the previous lesson) raises an immediate mechanical question: how do those pieces actually end up composed into one page a user sees as a single, coherent application? Two genuinely different models answer this, and the choice between them is one of the more consequential decisions in a micro-frontend architecture.

Module federation, conceptually

Module federation lets one independently built and deployed application (a "host") load code — a component, a whole page, a utility — from another independently built and deployed application (a "remote") at runtime, without either having been compiled together. Critically, it also lets multiple pieces agree to share a dependency — React, a design-system package — rather than each bundling its own copy, addressing directly the duplicated-dependency cost from the previous lesson, as long as every piece is configured to participate in that shared scope.

  1. Step 1

    Host application loads

    The main shell application starts, with its own core code already bundled.

  2. Step 2

    Host requests a remote's exposed module

    e.g. a 'checkout' micro-frontend exposes a Checkout component that the host wants to render at a given route.

  3. Step 3

    The remote's code is fetched at runtime

    Over the network, from wherever that independently-deployed piece is hosted — not bundled into the host's build.

  4. Step 4

    Shared dependencies are resolved

    If host and remote both declared React as shared, only one copy is actually used, rather than the remote bringing its own.

  5. Step 5

    The remote's component renders inside the host

    The user sees one coherent page, composed from pieces that were never built together.

The defining trait: the host doesn't know at its own build time exactly what code it will load — that's resolved when the page actually runs.

Build-time vs. run-time composition

Build-time composition combines pieces during a shared build or publish step — one micro-frontend is published as a versioned package, and another imports it like any other dependency, compiled together into one final bundle. This buys real compile-time safety: a type checker or test suite can catch a broken contract between the pieces before it ships. It costs exactly what full independence was supposed to avoid — an update to the published piece requires every consumer to bump the version and rebuild, which reintroduces a coordinated-release dependency between teams.

Run-time composition (module federation's actual model) loads each piece independently, in the browser, at the moment the page runs. Each piece can deploy on its own schedule — the host simply loads whatever version is currently live the next time a user visits — genuinely preserving independent deployability. The cost is equally real: there's no compile-time check that the host and remote still agree on their contract, so a breaking change on one side surfaces as a runtime failure, potentially in production, rather than a build failure caught before deploy.

Build-time composition

Compiled together from a versioned package. Compile-time safety on the contract; an update requires consumers to rebuild — a real coordination cost.

Run-time composition (module federation)

Loaded independently in the browser at page-load time. True independent deployability; contract breaks surface as runtime failures, not build failures.

Making the contract safe despite that trade-off

Because run-time composition gives up compile-time safety, teams that adopt it lean on other mechanisms to catch contract breaks earlier than production: published, versioned TypeScript types for what a remote exposes (even if the actual code loads at runtime, the shape can still be checked against); contract tests that run in each piece's own CI, verifying it still satisfies the agreed interface; and careful, deliberate versioning of what's exposed, with old versions kept available during a migration window rather than swapped instantly for everyone. None of these fully replace a compiler catching the mismatch immediately, but together they substantially narrow the risk window that run-time composition otherwise leaves open.

What to remember

  • Module federation lets independently built and deployed pieces load each other's code at runtime, including sharing common dependencies rather than each piece bundling its own copy.
  • Build-time composition trades independent deployability for compile-time safety on the contract between pieces; run-time composition makes the opposite trade.
  • Run-time composition's real risk is a contract break surfacing as a production runtime error rather than a build failure — mitigated by published types, contract tests, and careful versioning, not eliminated by them.
  • Choosing between the two is choosing which failure mode you'd rather have: slower releases with safety, or fast independent releases with an ongoing contract-discipline cost.

Check yourself

3 questions · pass 3/3 to unlock The Well-Organized Monolith as the Alternative

up to 50
  1. 1.What problem does module federation (conceptually) solve for micro-frontends?

  2. 2.What's the key difference between build-time and run-time composition of micro-frontends?

  3. 3.A team using run-time module federation ships a breaking change to a shared component's expected props from a remote micro-frontend, without coordinating with the host application's team. What's the direct risk?

3 left to answer