modern-react-spa

Chapter 25

Architecting a Multi-Team SPA

Architecting a multi-team React SPA — monorepo vs polyrepo at the organisational level, shared design systems as packages, and versioning strategies for shared code.

Published 2026-05-23

🎯 Chapter Goal — After this chapter you can decide between monorepo and polyrepo at the organisational level (the technical answer is Ch 19), structure a shared design system as a package, and pick a versioning strategy for shared code that survives multi-team usage.

🧭 Prerequisites — Ch 11 (component library), Ch 19 (monorepo mechanics).


🔹 25.1 Monorepo vs polyrepo — the organisational decision

Ch 19 covered the technical monorepo. This chapter is about whether your organisation should adopt it. Different question.

Monorepo pays organisationally when:

  • Multiple teams ship into the same product surface.
  • Cross-team refactors are frequent and need to land atomically.
  • Code review can span teams without political friction.
  • You have (or can build) a platform team to maintain the monorepo tooling.

Monorepo struggles organisationally when:

  • Teams have different release cadences and can’t unify.
  • Compliance / security boundaries require code isolation.
  • Team ownership is unclear → “everyone touches everything” pathology.
  • No platform team → toolchain rot.

The polyrepo answer is right when:

  • Teams are autonomous units with separate release calendars.
  • Shared code is small and rare (a couple of libraries).
  • Versioned interfaces are how teams already coordinate.

Most organisations of >30 frontend engineers benefit from a monorepo for the SPA. Most organisations of <10 engineers don’t need one.

CODEOWNERS — the single most important file

# .github/CODEOWNERS
/apps/web/                    @acme/web-team
/apps/admin/                  @acme/admin-team
/packages/ui/                 @acme/design-system
/packages/api-client/         @acme/platform
/tooling/                     @acme/platform
/turbo.json                   @acme/platform

Without CODEOWNERS, the “everyone touches everything” pathology kills the monorepo within a year. With it, ownership is loud and reviews land with the right people.


🔹 25.2 Shared design system as a package

Ch 11 is how to build the package. This section is where it lives organisationally.

Three patterns:

Inside the monorepo (most common)

packages/ui/ consumed by every app via the workspace protocol (Ch 19). Owned by a design-system team. Released internally on every PR; published externally only if there are external consumers.

Pros: instant feedback loop; atomic refactors across consumers; single source of truth. Cons: the DS team must serve every consumer; sometimes friction over breaking changes.

Separate repo, published library (less common in 2026)

A standalone @acme/ui repo. Apps consume via npm. Releases are versioned, documented, formal.

Pros: clear API contract; consumers control upgrade timing; works when consumers are outside the org. Cons: slow refactor cycle; consumers stuck on old versions; coordination cost.

Federated (rare, advanced)

The DS team ships its components via Module Federation (Ch 26). Apps consume at runtime, not build time. Updates roll out without app rebuilds.

Pros: instant rollout of fixes; one runtime version everywhere. Cons: complexity is significant; small bugs become big incidents (the DS is now infrastructure).

Default recommendation: in-monorepo for new organisations; separate published library if you have external consumers; federated only when you’ve outgrown both.


🔹 25.3 Versioning strategies for shared code

Three viable strategies:

Fixed versioning

Every package in the design system bumps together. @acme/ui@1.5.0, @acme/icons@1.5.0, @acme/tokens@1.5.0 — all on the same version. Consumers can be confident about compatibility.

Pros: simple; no peer-dependency math. Cons: “I only changed an icon” still bumps everything; signal-to-noise on the CHANGELOG suffers.

Independent versioning

Each package versions on its own cadence. @acme/ui@2.0.0 can coexist with @acme/icons@1.4.7.

Pros: accurate semver per package; meaningful CHANGELOGs. Cons: peer-dependency math; consumers need to track multiple versions.

Linked versioning (Changesets’ middle ground)

Like fixed, but only the affected packages bump. If @acme/ui changes, @acme/icons doesn’t bump — but if both change, they get the same new version.

Pros: clean signal; predictable for consumers. Cons: subtle to explain; less common; tools vary in support.

Recommended default: independent for monorepos with >3 packages and varied maturity; linked for tightly-coupled design systems with 2–3 packages.

Caret vs tilde vs exact pins

Range syntaxExampleAllowsUse when…
Caret ^^1.5.01.x.y, x ≥ 5Default for trusted-publisher libraries
Tilde ~~1.5.01.5.xConservative — only patch updates
Exact1.5.01.5.0 onlyWhen the lib has burned you with minor breakage
Workspace *workspace:*The local workspaceInternal monorepo packages (Ch 19)

For published @acme/* packages, use ^ if your DS team follows strict semver, ~ otherwise. For workspace-internal packages, always workspace:*.


🪤 Common Pitfalls

  1. Monorepo without CODEOWNERS → ownership rot within a year.
  2. Polyrepo with shared library that updates daily → consumers always 3 versions behind.
  3. Fixed versioning across many packages → noisy CHANGELOGs.
  4. Pinning internal deps to exact versions → manual bump on every release.
  5. No platform team for the monorepo → tooling becomes everyone’s part-time job and nobody’s full-time job.

✅ Recap

  • The organisational decision is separate from the technical one.
  • Monorepo for orgs > 30 frontend engineers with cross-team work; polyrepo otherwise.
  • CODEOWNERS is the single load-bearing file.
  • Design system inside the monorepo by default; published library if external consumers; federated only at scale.
  • Independent versioning is the safe default for >3 packages.

🔗 Further Reading

  • Vercel / Shopify engineering blogs — large monorepo case studies.
  • GitHub — CODEOWNERS reference.
  • Ch 19 (monorepo mechanics); Ch 11 (component library packaging).

In the book — not on the site

Each topic has an 🧠 Under-the-hood subsection — the algorithm, the data structures, what React DevTools surfaces, debugging recipes. Plus a 🧪 hands-on lab per chapter with a starter repo. Reserved for the book.

Topics in this chapter (3)
  1. 25.1 Monorepo vs polyrepo — the organisational decision
  2. 25.2 Shared design system as a package
  3. 25.3 Versioning strategies for shared code