Learn

Architecture & pattern guides

Read the idea, build it on a Grid, prove it in a challenge. Short, practical guides — illustrated with RodGrid's own nodes — on system design, design patterns, flow modeling, and how to get the most out of the app.

All articles

Micro-frontends: one page, many teams

How one web front end is split into slices that separate teams build, deploy and own, then composed back into one page: the shell, where composition happens, versioned releases a team ships and rolls back alone, a broken slice that leaves the page standing, shared design tokens, thin channels between slices, and when one app is still the better choice.

Nine days for a one-line fix

A property-listing site shows a monthly mortgage payment one dollar below the lender's quote, because the calculator rounds the wrong way. The fix is one line, reviewed in an hour. It reaches users nine days later.

Five teams work on the site: search, listings, mortgages, saved homes and accounts, each deploying its back-end services when it likes. The front end is one single-page application, released together every Tuesday. The fix missed Tuesday's cut, and the next release slipped two days because a search change broke an end-to-end test. The teams are independent everywhere except in the part users see.

Cut the page the way the teams are cut

A micro-frontend is a slice of a web front end that one team builds, tests, deploys and runs on its own, often together with the backend it calls. The mortgage team owns the calculator widget, the service behind it and the pipelines that ship them: a vertical slice by business area, not a layer. Conway's law observes that systems tend to copy the communication structure of the organisation that builds them; micro-frontends draw the code boundaries along the team boundaries on purpose.

Putting them back together is the job of the shell, also called the container or host application: a small app, often owned by a platform team, that holds routing, layout and the shared header, loads the slices a page needs and gives each a place, a slot. On a listing page, the listing slice takes the main slot, the calculator the sidebar, the saved-homes heart the header.

Where the pieces meet

Assembling the slices into a page, composition, can happen at different moments, and the moment decides how independent the teams are. At build time, each slice is a package the shell depends on: the simplest option, and it brings the problem back, because the calculator fix ships only when the shell is rebuilt and released. On the server or at the edge, a layer stitches each team's HTML fragment into one page before it reaches the browser; where and when HTML is built is the subject of Rendering strategies: SSR, SSG, CSR & the edge.

The property site composes in the browser, at runtime: the shell downloads each slice's JavaScript when the page needs it and mounts it in its slot, so a team ships without rebuilding the shell. Common mechanisms are module federation, introduced in webpack 5, where one separately deployed build loads modules that another build exposes; import maps, a browser feature that maps a module name such as mortgage to a URL; and web components, custom elements a slice registers under its own tag name, such as mortgage-calculator, so the shell only places the tag. An iframe isolates a slice further, with its own document, at the price of bridging sizing, routing and styling by hand.

One listing page, loaded

On a listing page, the browser first loads the shell from the CDN. The shell fetches a small manifest, cached for a minute, naming each slice's version: listing 41, mortgage 17, saved homes 8. It draws the layout, imports each slice from its versioned URL and mounts it, passing the listing id to the listing slice and the asking price to the calculator. Each slice then calls its own backend: the calculator asks the mortgage team's BFF for today's rates, a backend shaped for one experience (Backends for Frontends (BFF)).

The shell composes three slices

The shell reads the manifest, loads each slice's bundle from the CDN and mounts it in a slot. The mortgage team publishes through its own pipeline and calls its own BFF.

Shipping on a Wednesday

Now the rounding fix needs only the mortgage team's pipeline. It uploads the build under a new path, /mortgage/18/, beside version 17, never over it, so files under a version path never change and caches can keep them. Then it changes one manifest entry from 17 to 18. With the manifest cached for at most a minute wherever it is cached, new page loads get the fix within that minute, while pages already open keep 17 until they reload. No other team rebuilt anything.

Rollback is the same edit in reverse: point the manifest back to 17, whose files are still there. A different manifest for a share of users gives a staged rollout; rollout strategies are in Progressive delivery: blue-green, canary & rollback.

What a team cannot change alone is its contract with the shell: the slice's name, the function or element that mounts it, the values it accepts. If version 18 renames price to listPrice, the shell passes a value nobody reads and the calculator shows no payment. Treat the contract like a public API: add fields rather than rename them, and keep the old shape until the shell has moved (API versioning & backwards compatibility).

When the calculator throws

The site uses React, where an uncaught error while rendering unmounts the whole component tree: in the single app, a bad calculator release meant a blank page. So every slot gets an error boundary (inside the slice, when it mounts its own React root), a component that catches errors from what it wraps and renders a fallback instead: the sidebar shows a plain link to the lender's own calculator, and the listing stays. A boundary catches errors thrown while rendering, not those in event handlers or asynchronous code, which each slice still handles itself. The shell also caps each slice's download time and catches a failed import or mount, so a broken or hanging bundle leaves one slot showing its fallback. Serving a page with one part missing is a wider problem; The BFF tier: gateway, sessions & fan-out covers it on the server side.

A boundary does not separate what slices share in the browser: one main thread and one global scope. A slice that loops forever, leaks global CSS or overwrites a global variable still harms its neighbours. Shadow DOM, part of web components, keeps a slice's styles in; an iframe gives it its own document and global scope.

Five teams, one product

A month after the split, the slices disagree about which blue is the brand's, and users see a sloppy site, not five teams. The common remedy is shared design tokens, the named values behind the visual language (colours, spacing, type sizes, corner radii), and components built on them, published as a versioned module on the CDN. Each team adopts a new version on its own release, and the old one stays up meanwhile.

Shared code also has weight: if each slice bundles its own copy of the UI framework, the page downloads it several times. Module federation and import maps can serve one shared copy, as long as the slices agree on a compatible version.

Keep the seams thin

The saved-homes heart in the header has to know when the user saves a home in the listing slice. The first idea is a global store every slice reads and writes. It recreates the coupling the split removed: every slice now depends on the store's shape, and changing that shape is a release across teams.

Thinner channels hold up better. The URL says what the page is about. A slice announces what happened with a browser custom event, home-saved with the listing id, and whoever cares listens, unknown to the sender; the event's name and payload are still a contract. Values from the shell, like the asking price, carry inputs in. State inside one slice is that team's business; Unidirectional data flow: actions, store & view covers one way to organise it.

When one app is still better

Each slice brings its own bundle, requests and start-up work, so pages tend to get heavier than the same features in one app. Five pipelines, a manifest, a tokens module and the shell's loader are infrastructure someone runs, and debugs when a page breaks only with listing 41 and mortgage 18 together. That cost pays off when several teams keep blocking each other's releases. With one or two front-end teams, the same boundaries drawn as modules inside one app, each with an owner and one deploy for all, are the better trade in most cases (Modular Monolith).

A storefront on a Grid

Micro-Frontend Platform asks for this design for a storefront, in up to 14 nodes. Put a shell in front of the storefront's micro-frontends, give each its own CI/CD pipeline publishing versioned assets to the CDN, and put a BFF per experience behind them. Name the isolation mechanism on the shell, such as a per-slot error boundary, and add the design tokens as a versioned module on the CDN. A fix to one slice should touch only that team's nodes and one manifest entry.

Practice this