Skip to main content

Guide

Monorepo vs Single Repo for SaaS in 2026

Monorepo or single repo for your SaaS? When each makes sense, which boilerplates support each approach, and the Turborepo trade-offs explained for 2026.

StarterPick Team

TL;DR

Single repo (regular Next.js) for solo founders, early-stage SaaS, and products with one frontend. Monorepo (T3 Turbo or Turborepo) when you have multiple apps sharing code — web + mobile + marketing site, or web + API + workers. Most successful solo SaaS products start single-repo and evolve to monorepo only when the code-sharing problem actually exists.

What "Monorepo" Actually Means

A monorepo is a single Git repository containing multiple packages or apps:

my-saas/                     ← Single repo (just a Next.js app)
└── src/

my-saas-monorepo/            ← Monorepo (multiple apps + packages)
├── apps/
│   ├── web/                 ← Next.js web app
│   ├── mobile/              ← React Native (Expo)
│   └── marketing/           ← Astro or another Next.js app
├── packages/
│   ├── ui/                  ← Shared component library
│   ├── api/                 ← tRPC router (shared between apps)
│   ├── db/                  ← Prisma schema + client
│   └── config/              ← Shared TypeScript, ESLint config
└── turbo.json

The monorepo pattern exists to solve the code-sharing problem: when you have multiple deployable apps that need to share types, components, or business logic.

Single Repo: Simple, Fast, Focused

A standard Next.js app in a single repo is what most boilerplates ship:

starterpick-single/
├── app/                  ← Next.js App Router
│   ├── (auth)/
│   ├── (dashboard)/
│   └── api/
├── components/
├── lib/
├── prisma/
└── package.json

Advantages:

  • Zero tooling overhead (no Turborepo, no workspace configs)
  • Fast npm install, simpler CI
  • Every developer understands the structure immediately
  • Easy to deploy on Vercel with zero config

Disadvantages:

  • Code is tightly coupled — if you add mobile later, you copy code
  • Marketing site lives in the same deploy as the app
  • API and frontend can't be deployed separately

T3 Turbo is the most popular monorepo boilerplate. Built on Turborepo with TypeScript, tRPC, and Next.js.

t3-turbo/
├── apps/
│   ├── nextjs/           ← Main web app
│   └── expo/             ← React Native mobile
├── packages/
│   ├── api/              ← tRPC router
│   ├── auth/             ← NextAuth config
│   ├── db/               ← Prisma schema + client
│   └── validators/       ← Zod schemas shared between apps
├── turbo.json
└── package.json

The Code Sharing Pattern in Practice

// packages/api/src/router/post.ts
// This router runs on BOTH web and mobile — defined once
import { z } from 'zod';
import { createTRPCRouter, protectedProcedure } from '../trpc';

export const postRouter = createTRPCRouter({
  create: protectedProcedure
    .input(z.object({ content: z.string().min(1).max(500) }))
    .mutation(async ({ ctx, input }) => {
      return ctx.db.post.create({
        data: { content: input.content, authorId: ctx.session.userId },
      });
    }),

  feed: protectedProcedure.query(async ({ ctx }) => {
    return ctx.db.post.findMany({
      where: { authorId: ctx.session.userId },
      orderBy: { createdAt: 'desc' },
      take: 20,
    });
  }),
});

// apps/nextjs/src/app/dashboard/page.tsx
import { api } from '@/trpc/server';  // Server-side caller

// apps/expo/src/screens/feed.tsx
import { api } from '@acme/api/native';  // React Query on mobile

Same router, same types, same validation — no code duplication.

Turborepo Build Caching

// turbo.json — parallel builds with smart caching
{
  "$schema": "https://turbo.build/schema.json",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],  // Build packages before apps
      "outputs": [".next/**", "!.next/cache/**", "dist/**"]
    },
    "typecheck": {
      "dependsOn": ["^typecheck"]
    },
    "lint": {},
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

Turborepo caches build outputs. If packages/db didn't change, it doesn't rebuild. On a large monorepo, this saves 30-90% of CI time.


The Real Trade-Offs

Monorepo Adds Overhead

Setting up a monorepo correctly takes effort:

// packages/ui/package.json — every shared package needs this
{
  "name": "@acme/ui",
  "version": "0.0.1",
  "exports": {
    ".": {
      "import": "./src/index.tsx",
      "types": "./src/index.tsx"
    }
  },
  "scripts": {
    "build": "tsc",
    "lint": "eslint .",
    "typecheck": "tsc --noEmit"
  }
}

Every shared package needs its own package.json, export maps, TypeScript config, and lint config. Small issue, but multiplied across 5-10 packages.

CI Becomes More Complex

# Single repo CI — simple
- run: npm install
- run: npm run build
- run: npm run test

# Monorepo CI — need to be smart about what changed
- run: npm install
- run: npx turbo build --filter=[HEAD^1]  # Only build affected packages
- run: npx turbo test --filter=[HEAD^1]

Turborepo's --filter flag limits builds to changed packages. You need to understand this to avoid slow CI.


Decision Framework

Choose single repo if:

  • Solo founder or small team (1-3 devs)
  • One frontend (web only)
  • Moving fast and iterating on product
  • No immediate plans for mobile
  • Deploying on Vercel

Choose monorepo if:

  • You're building web + mobile from day one
  • Multiple teams working on different parts
  • Need a shared design system/component library
  • Marketing site and app need separate deploys
  • You expect the codebase to grow significantly

The honest rule: Don't add monorepo complexity until you feel the code-sharing pain. If you're copying types between two projects, it's time for a monorepo. If you're not, you don't need one yet.


OptionStackBest For
T3 TurboNext.js + Expo + tRPCWeb + mobile with shared API
NxAny stackEnterprise teams, complex build graphs
Turborepo standaloneAny stackTeams who want flexibility without T3 opinions
pnpm workspacesAny stackMinimal tooling, just npm workspaces

Starting Single, Going Monorepo Later

You can extract to a monorepo later if you start single:

# Create new monorepo
mkdir my-saas-mono && cd my-saas-mono
npx create-turbo@latest --example with-prisma

# Move your existing app
mv ../my-saas apps/web

# Extract shared packages
mkdir packages/db
mv apps/web/prisma packages/db/
# ... update import paths

It's work, but not catastrophic. Most successful SaaS products make this transition at Series A or when adding mobile.


Monorepo CI/CD in Practice

Turborepo's build caching is the most undervalued advantage of the monorepo approach. For a project with 5 packages where only one changed, Turborepo runs tests only for the changed package and its dependents. On large codebases, this reduces CI run time from 15 minutes to 3 minutes.

# GitHub Actions with Turborepo — smart CI
jobs:
  ci:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 2  # Need previous commit for turbo --filter

      - name: Install dependencies
        run: pnpm install

      - name: Run affected tests only
        run: pnpm turbo test --filter=[HEAD^1]  # Only test what changed

      - name: Build affected packages
        run: pnpm turbo build --filter=[HEAD^1]

For a single repo, every CI run builds and tests everything. For a monorepo with 5 packages, changes to packages/ui don't rebuild packages/db or apps/mobile. Over hundreds of CI runs, this time savings is significant.


Key Takeaways

  • Start with a single repo unless you're building web + mobile from day one — monorepo complexity is not justified for a solo product with one frontend
  • T3 Turbo is the best-supported monorepo boilerplate for Next.js + Expo React Native combinations; it provides the code-sharing patterns that make monorepos worth the overhead
  • Turborepo's build caching is the most practical benefit: only rebuild and test packages that changed, reducing CI run times significantly on large codebases
  • The migration from single repo to monorepo is possible but takes real effort — if you know you'll need mobile within 6 months, starting with T3 Turbo avoids that migration work
  • pnpm workspaces is a lower-overhead alternative to Turborepo for teams that just need package sharing without the full build pipeline — evaluate whether you need the caching or just the code-sharing
  • Shared component libraries in monorepos create design system discipline by default: when your component lives in packages/ui, changes there propagate to all apps and require intentional versioning
  • The biggest monorepo failure mode is premature abstraction: extracting to a packages/ui before you know what the shared components actually are leads to a component library that's wrong for both apps and needs rewriting
  • Remote caching with Turborepo (via Vercel Remote Cache or a self-hosted Turborepo remote) extends build caching to your CI environment and across team members' local builds — dramatically reducing build times on large shared codebases
  • When extracting an existing app into a monorepo, TypeScript path aliases (@acme/*) replace relative import paths across packages — updating imports is the most time-consuming part of the extraction, so mapping all package boundaries carefully before starting avoids disruptive rewrites mid-migration and keeps the team fully unblocked throughout the transition

Monorepo Anti-Patterns to Avoid

The monorepo structure creates opportunities for architectural mistakes that are harder to make in a single repo. Understanding the common anti-patterns before you adopt a monorepo structure saves significant refactoring time.

The most common anti-pattern: creating too many packages too early. Every packages/ directory entry adds configuration overhead — its own package.json, TypeScript config, ESLint config, and export declarations. Teams that create packages/utils, packages/config, packages/hooks, packages/types, and packages/constants before any of these have meaningful shared content end up with shallow packages containing three functions each. The rule: extract to a package when the code is actually shared between two different apps. Not "might be shared later" — actually shared right now.

The second anti-pattern: circular package dependencies. If packages/billing imports from packages/auth, and packages/auth imports from packages/billing for subscription status checks, Turborepo can't determine build order and your CI breaks in confusing ways. The fix is a clear dependency direction: utility packages at the base, then feature packages, then apps. Feature packages should only import from packages below them in the hierarchy.

The third anti-pattern: large apps with a monorepo wrapper but no actual code sharing. If apps/web is 95% of your codebase and packages/ contains only a tsconfig package, you're paying monorepo overhead with no sharing benefit. This is the pattern of "I'll need a mobile app eventually" thinking. Start single-repo and extract to monorepo when you actually have the sharing problem.

When Single Repo Wins Long-Term

Not every successful SaaS product ever needs a monorepo. Many products that grow to $1M ARR and beyond remain in single repos because their product never requires the code-sharing that justifies monorepo complexity.

SaaS products that stay web-only with a single customer-facing application genuinely don't benefit from monorepos. If your app is Next.js with an admin panel in the same codebase, you have two "apps" in the same repo without needing a monorepo structure — they share the same database client, component library, and API layer directly through file imports.

The developer onboarding cost also applies. A new engineer joining a monorepo team needs to understand Turborepo's task pipeline, workspace symlinks, and which packages are local versus published — a learning curve that doesn't exist in a single-repo project. For small teams where onboarding speed matters, the cognitive overhead of a monorepo is a real cost to consider against the code-sharing benefit.

Vercel's zero-config Next.js deployment becomes slightly more complex with monorepos. You need to configure the root directory and build command for each app separately in Vercel's project settings, and ensure Turborepo's remote cache is configured to share build artifacts with CI. For simple solo projects, the single-repo vercel deploy workflow is meaningfully simpler and eliminates an entire category of deployment configuration issues that monorepo setups introduce.

The pragmatic conclusion for most indie SaaS founders: start with a high-quality single-repo boilerplate, ship fast, and migrate to a monorepo if and when you hit the specific code-sharing problems that justify it. The migration is work, but it's predictable work. The sunk cost of a poorly chosen monorepo structure that you fight every day is much harder to recover from than a planned future migration.


Find monorepo and single-repo boilerplates on StarterPick.

Review T3 Turbo and compare alternatives on StarterPick.

See our T3 Stack review 2026 for the single-repo version of the T3 ecosystem.

Compare the best Next.js boilerplates regardless of repo structure: Best Next.js boilerplates 2026.

See how monorepo architecture fits the ideal SaaS stack: Ideal tech stack for SaaS in 2026.

Check out this starter

View T3 Turboon StarterPick →

The SaaS Boilerplate Matrix (Free PDF)

20+ SaaS starters compared: pricing, tech stack, auth, payments, and what you actually ship with. Updated monthly. Used by 150+ founders.

Join 150+ SaaS founders. Unsubscribe in one click.