DEV Community

Cover image for The Hidden Nightmare of Scaling Massive Angular Monoliths
Karol Modelski
Karol Modelski

Posted on • Originally published at javascript.plainenglish.io

The Hidden Nightmare of Scaling Massive Angular Monoliths

Everyone loves to talk about new frameworks.

Almost nobody wants to talk about what most senior Angular devs actually live in every day:

  • a 6+ year old enterprise app,
  • one Angular “app” that’s actually 40 products,
  • 20+ minute build times on CI,
  • circular dependencies hiding in every corner.

The docs tell you:

Use esbuild. Use Nx. Use module boundaries. Problem solved.

Then you try it in a real bank, healthcare, or legacy SaaS codebase and discover:

  • you didn’t adopt a tool,
  • you adopted a new category of pain.

Modern tooling doesn’t magically fix legacy monoliths. It just makes their problems visible — and sometimes even more expensive.


When “Just Switch to esbuild” Turns Into “Why Is This Build 30 Minutes Now?”

esbuild is a genuinely impressive piece of technology:

  • extremely fast bundling,
  • great for modern Angular apps,
  • lighter config than classic Webpack.

But real-world reports from large Angular monoliths are… mixed:

  • teams migrating from Webpack to esbuild see blazing fast local builds,
  • then discover production or CI builds get slower or more fragile because of chunking and splitting behavior in huge apps.

One Nx issue described:

  • a module federation setup where initial build time jumped from ~90 seconds to close to 30 minutes after changes — due to complex chunking strategies and the way dependencies were wired.

In another case, a team with “one of the largest monolithic Angular apps in the world” moved to esbuild and ended up with:

  • local dev: better,
  • production bundling: complicated chunk management,
  • final solution: two separate projects in angular.json — esbuild for dev, Webpack for prod.

That’s not a failure of esbuild.

It’s what happens when you bolt a modern bundler onto an app that:

  • was never designed with clear boundaries,
  • uses dynamic imports everywhere,
  • and treats lazy-loaded routes as a dumping ground.

f your architecture is a junk drawer, a faster compiler just lets you open the drawer quicker. It doesn’t organize what’s inside.


Nx, Monorepos, and the “Modularization Will Save Us” Myth

Nx and monorepos can be game-changers when used from the start:

  • incremental builds,
  • affected-only testing,
  • module boundaries enforced,
  • CI time dropping from ~20 minutes to under 2 in large, well-designed workspaces.

But migrating a giant, messy Angular app into Nx is where a lot of teams hit the Enterprise Monolith Trap.

You:

  • drop the app into an Nx workspace,
  • run nx graph,
  • discover a terrifying dependency diagram with cycles everywhere.

The docs tell you:

  • enable @nx/enforce-module-boundaries,
  • break circular dependencies by moving shared pieces into new libraries,
  • create a clean layer structure.

All correct.

But on a legacy app, that means:

  • weeks or months of refactoring just to make the tooling happy,
  • teams blocked on breaking cycles instead of shipping features,
  • regression risk across critical flows when you move “shared” code around.

Talks about modular monoliths show beautiful patterns:

  • clear domain layers,
  • private APIs,
  • top-down dependency rules.

In greenfield projects, that works brilliantly.

In a legacy enterprise frontend, you’re often starting from:

  • shared utils imported directly from /src/app everywhere,
  • cross-feature imports that ignore any sense of boundaries,
  • tight coupling between “unrelated” modules because of convenience imports.

Nx doesn’t turn your monolith into a modulith. It just stops pretending your monolith is fine — and forces you to pay down chaos you’ve been borrowing against for years.


The Silent Killer: Build Pipelines That Eat Your Day

The biggest productivity killer in large Angular apps usually isn’t the framework.

It’s the pipeline.

Symptoms you see in banks, healthcare, and legacy SaaS:

  • 10–30 minute builds in CI for every branch.
  • pipelines that must run extra code generators (proxies, API clients, etc.) before Angular even starts building.
  • flaky steps: tests, lints, and bundlers failing intermittently, forcing devs to rerun jobs.
  • “tiny” changes that still trigger full rebuilds because incremental builds weren’t designed in from the start.

Nx can help with:

  • caching,
  • incremental builds,
  • rebuilding only what changed.

But only if you’ve:

  • actually split code into buildable libraries,
  • wired all apps and libs to use the right executors,
  • aligned build target names across projects and libraries (or targetDefaults) so incremental executors can work.

Otherwise you get the worst of both worlds:

  • Nx complexity,
  • plus still building everything, every time.

And if your build also:

  • runs abp CLI proxies,
  • bundles multiple Angular apps,
  • packs everything into a deployment artifact,

you end up with a pipeline where nobody truly understands which step is slow, only that the whole thing is crushing morale.

You can’t have a healthy engineering culture if every experiment costs you a 30-minute CI penalty.


When “Just Modularize It” Becomes a Multi-Year Migration Trap

Enterprise talks make modularization sound simple:

  • extract domains into libraries,
  • define boundaries,
  • profit.

In reality, long-lived Angular apps usually have:

  • features that started as experiments and became core products,
  • modules that secretly depend on each other in both directions,
  • design systems and shared components that leak business logic.

When you try to modularize this:

  • you discover dozens or hundreds of circular dependencies.
  • you create “shared” libraries that quickly become new dumping grounds,
  • you end up either:  — endlessly slicing code into more libs, or  — merging libs back together because cycles are unmanageable.

The official Nx guidance on circular dependencies is sensible:

  • move small pieces from one project to another,
  • create a new shared lib for common dependencies,
  • or merge two libraries if they’re truly inseparable.

But in legacy frontends, everything feels inseparable:

  • because business logic and UI are tangled,
  • because features were never designed as domains,
  • because “shared” abstractions depend on concrete modules.

You can absolutely succeed with gradual modularization.

You just can’t pretend:

  • it’s a “quick Nx migration,”
  • or that tools alone will rescue you without rewriting some business logic and boundaries.

Large-scale migration isn’t a strategy. It’s what you call it when you don’t want to admit you’re rewriting while pretending you’re just ‘moving things around’.


The Enterprise Developer’s Reality: You’re Not Doing It Wrong

If you’re maintaining a massive Angular monolith, you’re probably:

  • doing on-call for a business-critical system,
  • juggling EOL reminders (Angular 19 end-of-life, libs that are no longer maintained),
  • forced to be cautious about every “cool” refactor because downtime has real cost.

When blog posts and conference talks say:

  • “Just adopt esbuild, it’s 10x faster,”
  • “Just move to an Nx monorepo,”
  • “Just break everything into micro frontends,”

they often ignore:

  • regulatory constraints,
  • long CI pipelines you can’t rewrite from scratch,
  • the risk of mass refactors in a system nobody fully understands anymore.

Your frustration is not a lack of skill.

It’s a rational response to:

  • brittle pipelines,
  • historical decisions,
  • and tools that were designed for idealized codebases.

Enterprise frontends aren’t broken because seniors are clueless. They’re broken because trade-offs compound — and tools arrived ten years after the architecture was decided.


What Actually Helps: Constraints, Not Silver Bullets

If you’re responsible for one of these monsters, here’s the boring but effective playbook grounded in what real teams report:

  1. Accept the monolith, then modularize within it
  • Think “modulith” before micro frontends: one deployable, internally modular.  — Introduce clear layers and boundaries gradually — not everything at once.
  1. Fix the worst pipeline bottlenecks first
  • Profile where time actually goes: bundling, tests, codegen, artifact uploads.  — Use Nx incremental builds where possible, even if only for part of the codebase at first.
  1. Use tools to reveal problems, not to declare victory
  • nx graph to see dependency chaos.  — @nx/enforce-module-boundaries to prevent new damage while you slowly untangle the old.
  1. Treat circular dependencies as design feedback, not just lint errors
  • if two libs constantly depend on each other, maybe they’re the same domain; merge them.  — if “shared” is depending on “feature,” you don’t have a shared lib — you have inverted design.
  1. Limit how many technologies you add to the party
  • don’t add module federation, custom bundlers, a new design system, and a new monorepo in the same quarter.  — every move that touches build + architecture at once multiplies risk.

The goal isn’t to turn your enterprise monolith into a conference talk. It’s to make it slightly less painful every quarter without blowing it up in production.


I’m an Independent Technology Partner for SMEs. If you’re looking to optimize your architecture or accelerate your MVP, let’s connect at karolmodelski.pl.

Top comments (0)