From co-located monolith to extracted microservices — why starting with a modular monolith on Vercel is the industry-endorsed path, and how to evolve when scale demands it.


1. The Design Decision

We are building a new application for a startup. The initial expectation is around 1,000 MAU at peak. My team, coming from enterprise backgrounds, proposed a microservices architecture. I understand their instinct — in large enterprises, microservices solve real problems around team autonomy, independent deployment, and fault isolation. But startups are a different beast. The ask is speed and cost, without compromising quality.

During my research, I identified a pattern that I believe gives us the best of both worlds: the discipline of service boundaries from day one, with the simplicity and speed of a monolith, and a clear, proven path to microservices when we actually need them.

This document captures that architectural design, maps it to established industry patterns, and provides the rationale for why I am recommending this approach over premature microservices.

1.1 What I Found

When I explored React deployments on Vercel, I noticed two options that share the same deployment model but differ in how tightly the frontend and backend are coupled:

  1. Option 1 — Co-located server-side services: Each page (say, login and checkout) has its own server-side logic running in Node.js on Vercel. The React UI and the backend service live together, deployed together. Each page is a self-contained unit.
  2. Option 2 — Headless API transition: The same co-located services are refactored to return JSON instead of rendering pages. The frontend becomes a pure API consumer. The deployment does not change. The URLs do not change. But the contract is now explicit.

The critical observation: if the application works and I need to scale, I can take any of these components and deploy them as independent Node.js services using Docker containers. The frontend does not change — it still calls the same endpoints. The BFF layer simply proxies to the new service.

This gives me a scale-out option without architectural rework.

1.2 Pattern Mapping

After deep research, I can confirm these observations map precisely to three well-documented, industry-endorsed patterns:

What I DescribedFormal Pattern Name
Option 1 — React pages with co-located server-side Node services on Vercel. Login and checkout each have their own backend.Modular Monolith + Vertical Slice Architecture — Next.js Route Handlers acting as a Backend for Frontend (BFF). Proponents: Simon Brown, Milan Jovanović, Sam Newman.
Option 2 — Same services return JSON. Frontend becomes headless. Deployment stays the same.API-First / Headless Architecture — Transition within the BFF layer. Officially documented in Next.js BFF guide and Vercel docs.
Scale Path — Extract components into independent Node/Docker services. Frontend unaffected.Strangler Fig Pattern + Service Extraction — BFF acts as the strangler proxy. Proponents: Martin Fowler, Sam Newman, Eric Evans (DDD).

Verdict: This is not just a valid pattern — it is the recommended approach by Martin Fowler (MonolithFirst), DHH (Majestic Monolith), and Sam Newman (Monolith to Microservices). Industry data from 2025–2026 shows 60% of teams regret premature microservices for small-to-medium apps, and 42% of organisations have consolidated services back.


2. The Three Patterns in Detail

2.1 Pattern One: Modular Monolith with Vertical Slices

When I described React pages with co-located server-side services on Vercel — login having its own backend logic, checkout having its own — I was describing a convergence of three established patterns.

Next.js Route Handlers as Backend for Frontend (BFF)

Next.js allows me to co-locate server-side API logic alongside React pages using Route Handlers (App Router) or API Routes (Pages Router). Each file can export HTTP method handlers — GET, POST, PUT, DELETE. These are server-side only; they never ship to the client bundle.

Vercel automatically deploys each Route Handler as an isolated serverless function. This means my login handler and checkout handler are already running as separate compute units, even though they live in a single codebase and deploy as one application.

The official Next.js documentation explicitly names this as the Backend for Frontend (BFF) pattern. The BFF layer intercepts requests, handles authentication, transforms data from upstream services, and acts as a proxy layer. The frontend never talks directly to downstream services.

Vertical Slice Architecture

The way I organised this — login as one complete vertical unit (UI + server logic + data access) and checkout as another — is formally called Vertical Slice Architecture. Each slice is a cross-sectional cut through all application layers for a specific feature. High cohesion within each slice, low coupling between slices.

The practical implementation in Next.js uses Feature-Sliced Design (FSD):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
app/
  (auth)/
    login/
      page.tsx          ← React UI (Server Component)
      route.ts          ← API endpoint (serverless function)
      actions.ts        ← Server Actions (form handling)
      db.ts             ← Data access for this feature
  (commerce)/
    checkout/
      page.tsx          ← Checkout UI
      route.ts          ← Checkout API (payment, cart)
      actions.ts        ← Server Actions
      db.ts             ← Data access for checkout
  shared/
    middleware.ts       ← Auth guards, logging
    db-client.ts        ← Shared database connection

Modular Monolith

The overarching architecture is a Modular Monolith — a single codebase and deployment organised into loosely coupled modules, each responsible for a specific business capability. Simon Brown, creator of the C4 model, argues that if you cannot build a well-structured monolith, microservices will not save you.

The key distinction from a traditional monolith: modules interact through well-defined interfaces only. No cross-module database access. No shared internal state. Each module owns its data schema and business logic.

2.2 Pattern Two: API-First / Headless Transition

When I described switching the co-located services to return JSON instead of rendering pages — making the frontend headless while the deployment stays the same — I was describing the API-First Architecture transition.

How This Works

  1. Starting state: Route Handlers serve server-rendered pages. The page.tsx files use React Server Components to fetch data and render HTML on the server. Tightly coupled, fast to build — ideal for early-stage product development.
  2. Transition: I refactor the Route Handlers to return pure JSON responses. The data-fetching logic stays in the same file, at the same URL path, deployed the same way. Instead of rendering HTML, the endpoints return structured data.
  3. Result: The frontend becomes a pure client — it fetches JSON and renders locally. The backend is now a headless API. Deployment on Vercel does not change. URLs do not change. But the contract between frontend and backend is now explicit and documented.

This is significant because once my backend returns JSON through defined endpoints, any client can consume it — web, mobile, third-party integrations. I have created the foundation for multi-client architecture without any infrastructure changes.

Why this matters at our stage: I can ship fast with server-rendered pages in week 1, then progressively decouple when we need mobile apps or partner integrations. The deployment cost is identical — Vercel does not care whether a Route Handler returns HTML or JSON. My team can make this transition feature-by-feature, not all at once.

2.3 Pattern Three: Strangler Fig + Service Extraction

When I described taking these components and deploying them as independent Node services in Docker — with the frontend remaining unaffected — I was describing the Strangler Fig Pattern combined with Service Extraction.

The Strangler Fig Pattern (Martin Fowler, 2004)

Named after the strangler fig tree that grows around a host tree and eventually replaces it. A new system sits in front of the old, starts as a pass-through proxy, and gradually routes functionality to new services until the old system is fully replaced.

How It Works in Our Context

  1. The BFF layer acts as the Strangler proxy. Because the frontend already talks to Route Handlers (the BFF), those handlers can transparently route to either co-located logic OR an external Docker service. The frontend never knows the difference.
  2. I extract one service at a time. Say checkout becomes a bottleneck at 50,000 MAU. I take the checkout Route Handler logic, deploy it as an independent Node.js Docker container, and update the Route Handler to proxy to it. Login stays co-located.
  3. The frontend is completely insulated. The API contract stays the same. The URLs stay the same. The frontend code does not change. Only the backend routing changes.

Service Extraction: The Mechanics

Martin Fowler and Sam Newman both emphasise that service extraction must be atomic — extract the logic AND the data AND redirect all consumers. A common anti-pattern is extracting logic but sharing the database, which keeps you coupled at the data tier.

The recommended approach from Domain-Driven Design (Eric Evans) is to identify Bounded Contexts — areas where a domain model is consistent and self-contained. Each bounded context maps naturally to a microservice. In our case, authentication and checkout are clearly separate bounded contexts.


3. The Evolutionary Architecture Path

What I have identified is not three separate patterns but a coherent evolutionary architecture strategy. Here is how the phases map to our startup’s growth:

Phase 1 — Build Fast: Modular Monolith with Vertical Slices

Single team, fast iteration, searching for product-market fit. Everything in one Next.js app, deployed to Vercel.

Scale: 0 – 10,000 MAU

Stack: Next.js 15+ · Vercel Serverless · Route Handlers · React Server Components · TypeScript

Phase 2 — Decouple When Needed: API-First / Headless Transition

Route Handlers return JSON. Frontend consumes APIs. Same Vercel deployment, same URLs. Enables mobile apps & partner integrations.

Trigger: Need for mobile apps, partner integrations, or multiple frontend clients.

Stack: JSON API contracts · OpenAPI spec · Same deployment

Phase 3 — Scale Selectively: Strangler Fig + Service Extraction

Extract hot-path services into Docker containers. BFF layer proxies seamlessly. Frontend untouched.

Trigger: Specific features need independent scaling, different tech stacks, or dedicated teams.

Stack: Docker · Kubernetes (optional) · Independent Node.js services · BFF proxy routing

3.1 Industry Endorsements

“Almost all successful microservice stories have started with a monolith that got too big and was broken up, while almost all cases where a system was built as a microservices system from scratch have ended up in serious trouble.”

— Martin Fowler, MonolithFirst

Basecamp has run as a majestic monolith since 2003. When growth demands partial decomposition, they use “The Citadel” pattern — keep the monolith at the centre, extract small “Outpost” services only for divergent behaviour.

— DHH, Basecamp / Hey.com

Even experienced architects struggle to get service boundaries right at the beginning. Building a monolith first lets you discover proper boundaries before microservices design becomes difficult to change.

— Sam Newman, Monolith to Microservices

3.2 Recent Industry Data (2025–2026)

  • 60% of teams regret microservices for small-to-medium applications
  • 42% of organisations that adopted microservices have consolidated services back
  • Modular monoliths cut costs by 25% versus microservices for teams under 10 developers
  • Microservices benefits only appear with teams larger than 10 developers
  • Operational cost: Modular monolith needs 1–2 ops engineers; equivalent microservices needs 2–4 platform engineers plus distributed operational burden

4. Why Microservices Are Wrong for 1,000 MAU

My team’s instinct is understandable. They come from enterprise contexts where microservices solve real problems. But the context is fundamentally different at our stage.

4.1 The Premature Decomposition Anti-Pattern

Industry experts have identified multiple anti-patterns that apply directly to a startup choosing microservices at this stage:

  • Pixie Dust Anti-Pattern: Assuming microservices will magically solve development problems. The most common anti-pattern.
  • Dirty Adoption Anti-Pattern: Adopting microservices before basic software development practices (CI/CD, monitoring, testing) are in place.
  • Architecture Cosplay: A startup with a handful of engineers and 1,000 users copying Netflix’s architecture without understanding why Netflix needed those choices.

4.2 What Microservices Actually Cost Us

For a startup at 1,000 MAU, microservices introduce:

  • Separate CI/CD pipelines for each service (when we need speed, not infrastructure)
  • Distributed system debugging (network failures, partial outages, tracing across services)
  • Service discovery, load balancing, health checking infrastructure
  • Data consistency challenges (eventual consistency, saga patterns, distributed transactions)
  • 2–4x more infrastructure engineering overhead than a monolith
  • Slower feature velocity: every feature that spans services requires coordination

The real risk: Microservices do not kill startups through technical failure — they kill them through slowed velocity. At 1,000 MAU we are searching for product-market fit. Every hour spent on service mesh configuration is an hour not spent on user feedback.

4.3 When Companies Actually Migrated

CompanyStarted MigrationScale at MigrationTrigger
Netflix2008 (after 10+ years as monolith)Millions of subscribersDatabase corruption caused multi-day outage
AmazonEarly 2000sMassive retail trafficOrganisational scaling — teams couldn’t deploy independently
ShopifyGradual over years$200B+ merchant salesPerformance hotspots in specific services
BasecampNever (still monolith)Millions of usersN/A — monolith handles the scale
GitHubSelective extraction only100M+ developersSpecific features needed independent scaling

None of these companies started with microservices. All ran monoliths for years before extracting services — and most still maintain a substantial monolith at their core.


5.1 Immediate Implementation (Phase 1)

  • Framework: Next.js 15+ with App Router
  • Deployment: Vercel (serverless functions with Fluid Compute for cost efficiency)
  • Architecture: Modular Monolith with Vertical Slices (Feature-Sliced Design)
  • Server Logic: React Server Components + Route Handlers + Server Actions
  • Language: TypeScript only (one language, full stack)

5.2 Design Principles to Enable Future Evolution

  1. Enforce module boundaries now. Each feature folder (auth, checkout, catalog) has a clear public API. No cross-module database access.
  2. Use Route Handlers as the BFF from day one. Even if they just call local functions today, the URL contract is established. Switching the backend behind the handler changes nothing for the frontend.
  3. Keep data schemas per module. Even in a shared database, use schema prefixes or separate Prisma models per feature. This makes future data extraction straightforward.
  4. Version internal APIs. Use TypeScript interfaces to define contracts between modules. When I extract a service, these interfaces become the API spec.

5.3 Vercel Cost Efficiency at Our Scale

Vercel’s Fluid Compute model is particularly well-suited for a 1,000 MAU startup:

  • In-function concurrency: A single function instance handles multiple concurrent requests — reported savings of up to 95% on compute bills.
  • Pre-warming: Scale-to-one instead of scale-to-zero prevents 33% of cold starts.
  • Global edge network: 70+ points of presence for low-latency globally, zero infrastructure management.
  • Pro tier limits: Up to 30,000 concurrent functions — far beyond what 1,000 MAU will ever need.

6. How I Am Framing This for the Team

The team has enterprise instincts, which is valuable. But the conversation needs to reframe from “what architecture is correct” to “what architecture is correct for this stage.”

6.1 Acknowledge Their Concern

“You are right that we will eventually need service boundaries. The question is when we pay that cost. By starting with a modular monolith on Vercel, we get all the benefits of clear boundaries — separate modules, clean interfaces, independent data schemas — without the operational cost of distributed systems.”

6.2 Show the Evolution Path

“This is not a dead-end. Next.js Route Handlers are already serverless functions on Vercel. When a specific feature needs independent scaling, we extract just that feature into a Docker container and the BFF layer proxies to it. The frontend does not change. We are not avoiding microservices — we are earning them.”

6.3 Use Industry Authority

“Martin Fowler, Sam Newman, and DHH all recommend this exact approach. Netflix ran as a monolith for 10 years before extracting services. Basecamp still runs as a monolith serving millions. The pattern we are following has a name: Evolutionary Architecture.”

6.4 Define the Migration Triggers

I am proposing we agree on concrete criteria for when extraction is warranted:

  • A specific service needs to scale independently (different compute, memory, or latency requirements)
  • A feature needs a different technology stack (e.g., ML service in Python while the rest is TypeScript)
  • A separate team will own and deploy the service on its own cadence
  • Deployment bottlenecks: the monolith takes too long to deploy and roll back

Until at least one of these triggers fires, microservices add cost without value.


7. Key References

Foundational Patterns

  • Martin Fowler — MonolithFirst
  • Martin Fowler — Strangler Fig Application
  • Sam Newman — Monolith to Microservices: Evolutionary Patterns (O’Reilly, 2019)
  • Simon Brown — Modular Monolith concept (C4 model creator)
  • DHH — The Majestic Monolith
  • Eric Evans — Domain-Driven Design: Tackling Complexity in the Heart of Software

Next.js Specific

Architecture Decision Frameworks