Week 24: Micro-Frontends & Advanced Architecture

The last week of Phase 6 zooms out from a single app to how large organizations structure many of them. You won't need most of this at a small scale — but recognizing when it's actually warranted is a genuinely valuable skill on its own.

Phase 6 of 7 Week 24 of 26 ~4 Hours Hands-on Exercise Included

By the end of this week, you'll be able to

  • Understand the tradeoffs of splitting a monolith frontend
  • Set up a shared, versioned library consumed by multiple applications
  • Make an informed, defensible call on monolith vs. micro-frontend for a given team

1. Monorepo Strategies (Nx) for Multi-App Workspaces

You built a basic multi-app workspace by hand back in Week 2. Nx extends that same idea with tooling built specifically for it — dependency-graph-aware builds (only rebuild what actually changed), consistent generators across apps and libraries, and enforceable boundaries between them.

terminal
npx create-nx-workspace@latest my-org --preset=angular-monorepo

cd my-org
nx generate @nx/angular:application admin
nx generate @nx/angular:application storefront
nx generate @nx/angular:library ui-kit
nx.json (excerpt) — enforcing boundaries
{
  "targetDefaults": {
    "build": {
      "dependsOn": ["^build"] // only rebuilds a library's dependents when the library itself changes
    }
  }
}

Nx can also enforce module boundaries — for instance, preventing a feature-specific library from importing directly from another unrelated feature, forcing genuinely shared code through an explicit shared library instead. That constraint is what keeps a large monorepo from decaying into every app quietly depending on every other app's internals.

2. Module Federation & Micro-Frontend Patterns

A monorepo still ships one deployable build per app. Module federation goes further — separate applications, built and deployed independently, that load parts of each other at runtime, in the browser.

conceptual shape
// The "host" shell app doesn't bundle the checkout feature at build time --
// it fetches it from another team's independently-deployed application, at runtime:

const CheckoutModule = await loadRemoteModule({
  remoteEntry: 'https://checkout.yourapp.com/remoteEntry.js',
  exposedModule: './Checkout',
});

The practical payoff: the checkout team can deploy independently of the shell team, without coordinating a shared release — each micro-frontend has its own build, its own deploy pipeline, and potentially its own release cadence. The cost: real runtime complexity — version mismatches between shared dependencies, and debugging across multiple independently-deployed applications instead of one.

3. Shared Design-System Libraries Across Apps

Whether or not you go as far as module federation, a shared UI library keeps multiple apps visually and behaviorally consistent — the Button from Week 7, published once, used everywhere.

libs/ui-kit/src/public-api.ts
export * from './lib/button/button';
export * from './lib/modal/modal';
export * from './lib/rating-stars/rating-stars';

A public-api.ts (or index.ts) that explicitly re-exports only what's meant to be consumed externally is worth being deliberate about — it's the library's actual contract with every app that depends on it, distinct from internal implementation files nothing outside the library should import directly.

4. Versioning & Publishing Internal Libraries

Inside an Nx monorepo, every app always uses the library's latest code — there's no version number to manage day to day. Once a library needs to be consumed outside the monorepo (a separate repository, a different team entirely), semantic versioning becomes necessary again.

semantic versioning, briefly
// MAJOR.MINOR.PATCH
// 2.4.1 -> 2.4.2  Patch: bug fix, no API change -- safe to upgrade blindly
// 2.4.1 -> 2.5.0  Minor: new functionality, backward-compatible -- safe to upgrade
// 2.4.1 -> 3.0.0  Major: breaking change -- requires the consumer to read a changelog

A private npm registry (or a tool like Nx's own release tooling) publishes internal libraries the same way public npm packages are published — the discipline of semantic versioning is exactly what lets consuming teams upgrade confidently, or hold back deliberately when a major version lands.

5. Choosing Monolith vs. Micro-Frontend for a Given Team Size

A pattern worth internalizing, since it recurs across this entire course:

  • Single team, single app — a monolith (everything from Weeks 1-23) is almost always correct. No coordination problem exists yet to solve.
  • Multiple teams, one app, frequent deploy conflicts — an Nx monorepo with clear module boundaries often solves this without full runtime micro-frontends at all.
  • Multiple teams needing genuinely independent deploy cadences, at real organizational scale — this is where module federation earns its complexity.

Micro-frontends solve an organizational problem (teams stepping on each other, coupled release cycles) far more than a technical one — a small team adopting module federation "for scalability" before there's a second team to actually decouple from is taking on real runtime complexity for a problem that doesn't exist yet.

6. A Reference Architecture

Putting Sections 1-5 together, a realistic setup for a mid-sized organization with a few product teams:

conceptual structure
nx-workspace/
├── apps/
│   ├── shell/              # thin host app: nav, auth, layout
│   ├── dashboard/          # one team's feature area
│   └── billing/            # another team's feature area
├── libs/
│   ├── ui-kit/             # shared design-system components (Section 3)
│   ├── auth/                # shared Week 23 auth logic, one implementation
│   └── shared-types/        # shared TypeScript interfaces across apps

Notice this doesn't necessarily require module federation at all — a well-structured Nx monorepo with shared libraries already solves most of the coordination problem. Reach for runtime module federation specifically when teams need independent deployment, not just independent development within a shared repo.

7. Hands-on Exercise

Hands-on

Split the dashboard app into two independently-deployable micro-frontends

Take your dashboard app and genuinely split it, sharing common UI.

Requirements:

  1. Set up an Nx workspace with two applications — a shell (host) and a projects feature app — plus a shared ui-kit library containing at least your Week 7 Button and Modal.
  2. Configure module federation so shell loads projects at runtime rather than bundling it at build time.
  3. Build and serve both applications independently, and confirm the shell successfully loads the remote projects module at runtime.
  4. Deliberately introduce a version mismatch in a shared dependency between the two apps, observe what breaks, then fix it — this is the "real runtime complexity" Section 2 warned about, made concrete.
  5. Write a short decision record (a few sentences) arguing whether this split would actually be justified for a real team of your choosing, using Section 5's framework.
Hint

Step 4 is deliberately meant to be frustrating — that friction is the actual lesson. If it feels like more trouble than it's worth for a two-app toy project, that's exactly the signal Section 5 describes for when not to reach for this pattern in a real, smaller-scale app.

8. Knowledge Check

Four quick questions. Expand each to check your answer.

Q1

What does Nx's module-boundary enforcement actually prevent, and why does that matter as a monorepo grows?

It prevents one feature library from directly importing another unrelated feature library's internals, forcing genuinely shared code through an explicit, intentional shared library instead. Without this, a large monorepo tends to accumulate tangled, undocumented cross-feature dependencies over time, making it increasingly risky to change any one part without breaking something unexpected elsewhere.

Q2

What's the key difference between an Nx monorepo and true module federation, in terms of deployment?

An Nx monorepo can house multiple apps and shared libraries in one repository with coordinated tooling, but each app still typically builds and deploys as its own complete unit. Module federation goes further — separate applications are built and deployed completely independently, then combined at runtime in the browser, meaning one team can ship a change without any coordinated release with the other.

Q3

Per this lesson, what kind of problem do micro-frontends primarily solve — technical, or organizational?

Organizational — they primarily address multiple teams needing independent deployment cadences without coordinating shared releases. A single team working on a single app gains little from the pattern and takes on real runtime complexity (version mismatches, cross-application debugging) without the coordination problem it's designed to solve.

Q4

Bumping a shared library from 2.4.1 to 3.0.0 — what does that version jump signal to a consuming team, per semantic versioning?

A breaking change — something in the library's public API changed in a way that isn't backward-compatible, so the consuming team should read the changelog and expect to update their own code before upgrading, rather than pulling in the new version blindly the way they might for a patch or minor release.