Part 4 · 3 chapters · ~25 min
Frontend at Scale
The monorepo with its package graph, affected-only tasks, remote cache and atomic change, microfrontends and module federation as an organisational tool with costs in bytes and coherence, and distributing a design system (tokens, components, semver honoured, codemods, channels, adoption) with ownership written down.
13
The monorepo
one repo, many packages, one graph
- Layout:
apps/(web, admin, mobile-web),packages/(ui-kit, tokens, api-client, utils),tooling/(eslint and tsconfig presets); one lockfile; a workspace (pnpm, npm, yarn) that links packages from source. The package boundary replaces part 0's folder boundary: a name, exports, dependencies, a public API. - The package graph has no cycles (the runner refuses them). A change to tokens affects ui-kit affects every app: the task runner (Turborepo, Nx; Bazel at the very large) computes the affected set from the diff and runs build, test and lint for those only, in topological order, in parallel where independent.
- The remote cache: task outputs stored by a hash of inputs (sources, dependencies' outputs, env, task config). A PR that touched one app restores everything else: part 1's four minutes becomes forty seconds; local builds hit the same cache. A wrong cache key is a stale build, so env vars belong in the key.
- Internal versioning: consumed from source at the workspace version; no publish, no semver. A breaking change to ui-kit is one PR that updates every consumer (the graph lists them), reviewed by their owners through CODEOWNERS, landing atomically. Atomic cross-package change is the monorepo's main gift.
- External versioning: changesets declare patch, minor or major per PR; CI aggregates into versions and changelogs on release. A package can have internal consumers from source and external ones from the registry at once.
- Costs: one toolchain for everyone (upgrades happen to all at once); CI that must stay incremental; git scale limits at the top (sparse checkout, a VFS); ownership that must be written down or everything is everyone's.
THE MONOREPO
many packages, one graph, one toolchain, and a task runner that knows what changed
swipe the figure sideways, or tap expand for full screen
1/6
the layout
The layout: apps/ (web, admin, mobile-web), packages/ (ui-kit, tokens, api-client, utils, config), tooling/ (eslint-config, tsconfig). Each package has its own package.json with a name (@org/ui-kit), exports, and dependencies; the workspace (pnpm, npm or yarn workspaces) links them so an app imports the package from source without publishing.
14
Microfrontends and module federation
the shapes and the mechanisms
- A shell (navigation, auth, layout, telemetry, the shared-dependency policy) mounts remotes into slots: by route (/inbox is team A's remote) or by fragment (the header is one remote, the recommendations panel another). Runtime composition in the browser, or at the edge or server. A monorepo with one build is packages, not microfrontends; the difference is the deploy.
- Module federation (webpack 5, Rspack, a Vite plugin): a remote exposes modules at a URL with a manifest; the host loads them at runtime; shared dependencies are declared with version ranges (one React if the ranges agree, two if not). The remote deploys independently; the contract is the exposed module's props plus the shared ranges.
- Import maps map bare specifiers to URLs for plain ESM remotes: the standards version, fewer features, no bundler coupling. Iframes: the strongest isolation and the worst integration; for third parties, not for your own teams.
the costs, and when
- Bytes: each remote has its own chunks; shared dependencies deduplicate only when versions align; a user who visits three teams' routes pays for three vendor sets if they diverged. A shared-dependency policy (one React major across teams, upgraded together) is the price of not paying twice. The FSD course's budgets apply to the composed page.
- Coherence: three teams, three button styles, three expired-session behaviours. The design system as a versioned package and a platform team that owns the shell keep the product one product; without them, microfrontends are a slower way to build three.
- When: five or more teams with conflicting release cadences and a platform team for the shell. When not: one team, or teams a monorepo with affected-only CI can serve (most of the independence, none of the runtime cost). "Sometimes the deploys conflict" is a monorepo; "constantly, and it costs us releases" is microfrontends. It is an organisational tool; the technology follows the org chart.
MICROFRONTENDS AND MODULE FEDERATION
independent deploys for independent teams, paid for in bytes, coherence and a shell
swipe the figure sideways, or tap expand for full screen
1/6
the shapes
The shapes: a shell app that owns the frame (navigation, auth, layout) and mounts remotes into slots: by route (the /inbox route is team A's remote; /billing is team B's) or by fragment (the header is one remote, the recommendations panel another). Runtime composition (the browser loads remotes) or build-time (a monorepo with one build: not microfrontends, just packages).
15
Distributing a design system, and ownership
the design system as a dependency with a release process
- Tokens (colour, spacing, type, motion) from one source compiled to CSS variables, TS constants and native formats; themed by overriding variables; versioned alone so a colour fix is not a component release.
- Components on tokens, published as tree-shakeable ESM with types, a CSS strategy that does not fight the consumer (modules or vanilla-extract over the variables, no global class names), stories as documentation and visual regression tests.
- Versioning honoured: patch, minor, major by semver; changesets and changelogs; a deprecation that warns for a minor before removal in the next major. One broken minor and consumers pin; a pinned design system is a fork.
- Upgrading forty apps: a codemod with every breaking change, bot-opened upgrade PRs, a six-month support window for the previous major. The system team pays the upgrade cost once rather than forty teams paying it separately.
- Channels from one build: a registry package for separate repos, a workspace package for the monorepo, a CDN ESM-plus-CSS build at a versioned URL so microfrontends share one copy.
- Adoption measured: version per app, component usage per app, custom CSS overriding the system (the gap between system and product). The team's metrics are "percentage of UI from the system" and "release to full adoption".
ownership
- CODEOWNERS per directory or package: a review from the owner is required; an unowned path is a bug in the file. Ownership is written down because the repo does not imply it.
- A platform team owns what every product team depends on: the shell, the build pipeline and CI, the design system distribution, telemetry, the shared-dependency policy, the upgrade bots. Product teams own features. The Platformization course is about that team's work and its funding.
- The test of an ownership model: when the build breaks at 2 am, when a dependency has a CVE, when the design system needs a major, is there a name? If the answer is "whoever notices", the model is missing.
DISTRIBUTING A DESIGN SYSTEM
tokens, components, and the versioning that lets forty apps upgrade without a flag day
swipe the figure sideways, or tap expand for full screen
1/6
tokens
Tokens: one source (tokens.json, in the W3C design tokens format or Style Dictionary's), compiled to CSS variables (--color-primary-600), TS constants (tokens.color.primary[600]), and platform formats (Swift, Kotlin) for native apps. Themed by overriding the variables; a theme is a token set. Tokens change rarely and ship as their own package so a colour fix does not require a component release.