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.
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
{
"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.
// 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.
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.
// 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:
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
Split the dashboard app into two independently-deployable micro-frontends
Take your dashboard app and genuinely split it, sharing common UI.
Requirements:
- Set up an Nx workspace with two applications — a
shell(host) and aprojectsfeature app — plus a sharedui-kitlibrary containing at least your Week 7ButtonandModal. - Configure module federation so
shellloadsprojectsat runtime rather than bundling it at build time. - Build and serve both applications independently, and confirm the shell successfully loads the remote projects module at runtime.
- 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.
- 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.
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?
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?
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?
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?
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.