SaaS Development2 July 2026 · 8 min read

Refactoring a Monolithic SaaS Into Modular Boundaries in 2026

Most SaaS teams that reach for microservices actually need honest module boundaries instead. Here is how to find bounded contexts, extract them safely, and know when a monorepo tool earns its keep.

Refactoring a Monolithic SaaS Into Modular Boundaries in 2026

Every SaaS engineer hits the same wall eventually: the codebase runs, ships, and makes money, and every change to it takes longer than the last one. Refactoring a monolithic SaaS into modular boundaries is the fix nobody wants to schedule, because it produces zero visible feature for weeks. I've done this exact rebuild-in-place twice now, once on a codebase nobody else could safely touch anymore, and the lesson both times was the same: you don't need microservices, you need honest boundaries inside the one deployable you already have.

This isn't a pitch for a rewrite. Most teams that reach for microservices when their monolith gets unwieldy actually need something smaller and much less risky: a modular monolith, where the code is organized into real, enforced boundaries but still ships as one thing. No new servers to provision. No deployment pipeline to redesign. Just boundaries, enforced in code, inside the deployable you already have. The SaaS MVP stack I recommend for a greenfield build is deliberately monolithic on day one, because a modular monolith is where almost every growing SaaS should land before it earns the operational cost of splitting into services.

Key Takeaways

  • A monolith doesn't need microservices to fix its coupling problem. It needs module boundaries enforced in code, not folders.
  • Organizations consolidating microservices back into fewer deployable units rose from 23% to 42% between 2024 and 2026, according to a 2026 analysis by developer Daniel Jeong on modular monolith adoption. The pendulum swung, and most teams never needed to swing it in the first place.
  • Bounded contexts come from asking which parts of the codebase change together and which teams use different vocabulary for the same nouns, not from drawing boxes on a whiteboard.
  • Extract in this order: pure side-effect modules first (notifications, audit logging), core business logic last. Data isolation follows the same order.
  • A shared database across "separated" services is the distributed monolith trap. You inherit the network calls without gaining independent deploys.
  • Nx and Turborepo solve a build-orchestration problem, not a boundary problem. A modular monolith works in a single package with disciplined imports before it ever needs either tool.

When Does a SaaS Codebase Actually Need Module Boundaries?

Two vines from different plants tightly entwined around a single strained, cracked wooden trellis stake, illustrated in a botanical-tech style against a dark circuit-board backdrop

A SaaS codebase needs module boundaries once two unrelated features can no longer change independently without both breaking. That's the whole test. Everything else, team size, line count, deploy frequency, is a proxy for that one symptom.

On Callidus, the multi-tenant clinic SaaS platform I built for UK aesthetic clinics, the trigger was six roles: super admin, owner, admin, manager, practitioner, receptionist, each with different access to clinical data, financial data, settings, and billing. Inline role checks scattered through components rot the moment the access matrix changes even slightly. So the fix wasn't a services split. It was a set of access helpers, hasClinicalAccess, hasManagerAccess, hasAdminAccess, that every screen and every Firestore rule calls through instead of inlining its own logic. That's a module boundary. It has nothing to do with how many servers the app runs on.

Have you ever opened a file to fix one small thing and found six unrelated features tangled through it? Closed the laptop instead of touching it? That reflex, avoidance dressed up as prioritization, is usually the first honest signal that boundaries are missing.

How Do You Find the Bounded Contexts Hiding in Your Monolith?

A glowing seed pod isolated inside a clear glass capsule at the center of a dense wall of tangled, chaotic ivy, botanical-tech illustration

You find bounded contexts by asking which parts of the code change together, not by drawing an architecture diagram first. Milan Jovanović's refactoring approach, laid out in his write-up on overgrown bounded contexts, starts with exactly this question, then checks whether different parts of the team use different vocabulary for what looks like the same entity. If billing calls it a "subscription" and support calls it an "account," that's a seam, not a synonym.

His extraction order matters more than the mapping exercise. Start with the modules that are pure side effects, notification sending is the classic first cut, because nothing else depends on their internals, only on the event that triggers them. Billing shouldn't even need to know notifications exist; it just needs to emit an event and walk away. Core business logic, the stuff every other module actually depends on, comes last, once you've built the muscle of extracting things safely.

Data isolation follows the same order. Give each module its own schema and its own dedicated database role, one write path, and replace any cross-module join with a read-only view or a subscriber-owned read model, per Jovanović's data-boundary guidance. Resist the urge to query across schemas just because the tables sit in the same physical database. That urge is exactly what turns modules back into one tangled thing wearing separate folder names.

The Distributed Monolith Trap

Three separate potted plants on a table with roots diverging above ground but all three connected underground through one shared, cracked clay pipe glowing at the crack, botanical-tech illustration

The fastest way to make a monolith worse is to split its code into "services" while every service still reads and writes the same shared database. You get every cost of a distributed system, network hops, new failure points, cascading redeploys, and none of the benefit, because nothing actually deploys independently. One analysis calls the shared database "the anchor chaining you to your monolith," which is the most accurate one-line description of the failure mode I've read.

Do You Need Nx or Turborepo for a Modular Monolith?

You don't need Nx or Turborepo to start a modular monolith at all, because plain workspace boundaries and disciplined imports handle the job first. Reach for either tool once build times or dependency-graph complexity actually hurt, which for most SaaS teams is well after the boundary work is done.

Monorepo tooling is now genuinely mainstream. Sixty-three percent of companies with 50 or more developers run one, per daily.dev's 2026 monorepo tooling survey, and the two tools solve different-sized problems.

| Tool | Best fit | What it actually buys you | Where it falls short | |---|---|---|---| | Plain npm/pnpm workspaces | Solo builder, one team, under ~10 packages | Zero config, fast enough | No task caching, no dependency-graph visualization | | Turborepo | 5-50 packages, JS/TS-only, Vercel-deployed | Remote caching, simple pipeline config | Less suited to multi-language or enterprise-scale graphs | | Nx | Large orgs, complex dependency graphs, code generation needs | Architectural rules, generators, multi-language support | Steeper setup, more to learn for a small team |

Mercari's Web Platform Team rolled out a self-hosted Turborepo remote cache in February 2026 and cut PR build durations by 30% overall, with individual task durations down 50%, according to the same daily.dev report. That's a real number, but it's a caching win on top of boundaries that already existed. Buy the tool after the discipline, not instead of it.

Shipping the Boundary Extraction Without a Rewrite

  1. Pick the lowest-risk module first. Notifications, audit logging, and anything that's a pure side effect of another action are the safest starting points because nothing else needs their internals.
  2. Give that module its own schema or table prefix, plus a dedicated database role that only it can write through.
  3. Replace every direct call into the module with a domain event. The calling code shouldn't know the module exists, only that an event fired.
  4. Add a boundary check to your CI pipeline. A lint rule or an architecture test that fails the build on a cross-module import turns the boundary from a convention into a fact.
  5. Repeat on the next-lowest-risk module. Save the module everything else depends on, usually billing or the core entity model, for last.
  6. Only reach for Nx, Turborepo, or a service split once build time or team-ownership friction, not code tidiness, is the actual bottleneck.

None of this requires stopping feature work for a quarter. Each step ships on its own, and the B2B SaaS codebase gets safer to change after every single one, not just at the end.

Picture the moment it usually goes sideways. Wednesday afternoon, three sprints into a boundary extraction, you rip out a direct import between two modules and the build breaks somewhere that has nothing to do with either one. Trace it far enough and there's almost always a third file quietly relying on both internals, written by someone who left the team, referencing a shortcut nobody documented. That's usually the actual reason teams stall halfway through this kind of work: the boundary you're drawing keeps surfacing debt nobody remembers writing, not the boundary itself.

Let me back up on one thing. Extraction order isn't really about risk tolerance. It's about which modules can fail loudly during the transition without taking a paying customer's data with them. Notifications failing is an annoyance. A multi-tenant billing module failing mid-extraction is a support queue and a refund.

If you're still deciding between shipping fast on a no-code stack versus writing the code yourself, this whole exercise is premature. Module boundaries are a problem you earn by having enough paying customers that a monolith actually got tangled. Most teams reach for the architecture book before they've reached the problem the book is solving.

Pick one module in your codebase today, the one you're most afraid to touch, and ask what it would take to make it depend on an event instead of a direct call. That's the whole first step. Everything else in this post is just what to do after.

DL

Dusko Licanin

Full-Stack Developer · Banja Luka, Bosnia

Full-stack developer shipping SaaS MVPs, web apps, and mobile apps 2× faster than agencies using AI-augmented workflows. Live portfolio: BookBed, Callidus, Pizzeria Bestek.

Frequently Asked Questions

What is a modular monolith in SaaS architecture?

A modular monolith is a single deployable application whose code is split into strictly bounded modules that communicate through explicit interfaces and events, not direct calls. It ships as one unit, one build, one deploy, one runtime, but internally behaves like a set of services that happen to share a process. The difference from a plain monolith is enforcement: a lint rule or an architecture test, not a folder-naming convention, blocks a pull request the moment one module reaches directly into another's internals. Most SaaS products that outgrow "everything imports everything" belong here, not in microservices, because a modular monolith buys the coupling discipline without the network cost.

How do you identify bounded contexts in an existing SaaS codebase?

Identify bounded contexts by asking which parts of the code change together and which teams use different words for what looks like the same entity. Start with an Event Storming session or a plain whiteboard pass over the domain's real workflows, not the existing folder structure, since folders usually mirror history rather than the actual seams. Milan Jovanović's [refactoring write-up](https://www.milanjovanovic.tech/blog/refactoring-overgrown-bounded-contexts-in-modular-monoliths) frames the test well: if billing and support use different vocabulary for what your database calls the same row, that's a boundary, not a naming inconsistency. When the boundary is genuinely unclear, keep the module coarser rather than splitting prematurely.

When should you refactor a SaaS codebase instead of rewriting it?

Refactor instead of rewriting whenever the business logic inside the monolith still works and only the coupling between features has become the problem. A rewrite throws away years of edge-case handling that nobody remembers documenting, and the "big bang" version of it fails far more often than it succeeds. Incremental extraction, module by module, starting with pure side effects like notifications, ships value every week instead of freezing feature work for a quarter. Reach for a rewrite only when the underlying data model itself is wrong, not just tangled, which is a much rarer situation than most teams assume.

Do you need Nx or Turborepo to run a modular monolith?

No, a modular monolith runs fine in a single package with disciplined imports before you need any monorepo tool at all. Nx and Turborepo solve a build-orchestration problem, caching, task graphs, multi-package coordination, not a boundary-enforcement problem. Monorepo tooling is genuinely mainstream now, used by roughly 63% of companies with 50 or more developers according to [daily.dev's 2026 tooling survey](https://daily.dev/blog/monorepo-turborepo-vs-nx-vs-bazel-modern-development-teams/), but that adoption follows team growth and build-time pain, not the initial boundary work. Add the tool once splitting packages actually slows down CI, not before.

How is a modular monolith different from microservices?

A modular monolith enforces boundaries in code inside one deployable unit, while microservices enforce boundaries by deploying each piece separately over a network. The modular version gives up independent scaling and independent deploys per module, but it also skips the network calls, service discovery, and distributed tracing that microservices require from day one. The real risk sits in between the two: splitting code into "services" that still share one database inherits every cost of the network without gaining any deployment independence, [a failure mode known as the distributed monolith](https://www.42coffeecups.com/blog/monolith-to-microservices-migration). A modular monolith done properly is often the more honest architecture for a team under roughly twenty engineers.