Skip to content

Every product that survives its first year hits the same question: the codebase has grown, more people are touching it, and someone suggests splitting it up. The two words that come up are monorepo and micro-frontends. They solve different problems, and picking the wrong one costs months.

This is the way I think it through, from working on a digital health platform built as a micro-frontend monorepo and on enterprise platforms that stayed as one deployable.

Start with the problem you actually have#

Before any diagram, write down what hurts today. It is usually one of these:

  • Drift. Three apps ship three different "primary" blues, two date formats and two copies of the same validation.
  • Blocked releases. One team's half-finished feature holds back another team's fix because everything ships together.
  • Slow builds and slow tests. Every change rebuilds and retests the world.
  • Nobody owns anything. A folder has twelve contributors and no clear boundary.

Drift and ownership are organisation problems. Blocked releases and slow builds are deployment problems. Monorepos mostly fix the first group; micro-frontends mostly fix the second. Mixing them up is where teams go wrong.

When a monorepo earns its keep#

I like a monorepo when several apps share a design system and a set of utilities. One version of the button, one lint config, one place to bump TypeScript. People underestimate how much drift hurts until they have to fix the same bug in three places.

What makes it work in Angular:

  • Libraries with clear boundaries. A ui library for shared components, a data-access library per domain, feature libraries that depend on both. Enforce the direction of imports with lint rules so a feature can't reach into another feature.
  • One toolchain. Shared ESLint, Prettier, pre-commit hooks and a single CI pipeline. New developers set up once.
  • Affected-only builds. Build and test only the projects a change touches; otherwise the monorepo becomes the slow build you were trying to avoid.

A monorepo does not mean every app deploys together. It means they live together.

When micro-frontends are worth the cost#

Micro-frontends are useful when teams truly need different release trains, or when one team's bundle must not be allowed to take down another's. On the health platform, separate modules for monitoring, consultations and vitals were owned by different distributed teams and shipped on their own schedules. That is the case for it.

The cost is real:

  • Shared state, routing and authentication need a contract between shells and remotes.
  • Each remote brings its own bundle, so duplicated dependencies and version skew need watching.
  • Debugging a problem that crosses two remotes is slower than debugging one app.
  • Config-driven UI and localisation have to be designed to work across the boundary from day one.

If no one is asking for independent deploys, you are paying this cost for nothing.

Deciding in order: Pain (Drift and ownership, or blocked releases and slow builds?), then Boundaries (Tighten folders and imports inside one app first), then Monorepo (Shared UI, utilities and tooling; apps still deploy separately), then Micro-frontends (Only when teams need separate release trains)PainDrift and ownership, or blocked releases and slow builds?BoundariesTighten folders and imports inside one app firstMonorepoShared UI, utilities and tooling; apps still deploy separatelyMicro-frontendsOnly when teams need separate release trains
Deciding in order

My default: tighten boundaries first#

Most apps don't need either yet. They need the structure they already have to be enforced:

  1. Feature folders, not type folders. loans/, reservations/, reports/, each with its own components, store and API service, instead of components/, services/, models/ shared by everything.
  2. Lazy-loaded routes per feature. This gives you most of the build-size benefit of a split without any of the infrastructure.
  3. A thin shared layer. Only truly shared things go in shared/: the design system, HTTP interceptors, auth. If a "shared" thing is used by one feature, move it back.
  4. Lint rules on imports. A feature may import from shared/ and its own folder, nothing else.

Do this and you often find the pressure to split goes away. If it doesn't, you now have clean boundaries, which is exactly what a monorepo or a micro-frontend split needs anyway. Splitting a tangled app just distributes the tangle.

What to take away#

  • Name the pain before naming the architecture.
  • Monorepo for shared code and tooling; micro-frontends for independent releases. They are not the same decision.
  • Tighten boundaries inside one deployable first. Split later if team structure or performance forces your hand, not because the diagram looked good in a conference talk.