Skip to main content

Guide

Best Monorepo Boilerplates in 2026

Monorepos enable code sharing between web, mobile, and backend. Compare the best monorepo boilerplates — Turborepo, Nx, T3 Turbo, and multi-app starters.

StarterPick Team

TL;DR

T3 Turbo is the best monorepo boilerplate for full-stack TypeScript — web (Next.js) + mobile (Expo) sharing tRPC types, Prisma schema, and UI components. Turborepo's official starter is best for custom monorepos. Nx handles very large codebases (10+ apps, 50+ developers). Choose a monorepo only when you have a concrete need to share code between multiple deployments — not speculatively.

Monorepos: When One Repo Beats Many

A monorepo keeps multiple projects (web app, mobile app, shared UI library, API) in a single git repository. The payoffs:

  • Shared TypeScript types — Change a model once, TypeScript errors appear everywhere that needs updating
  • Shared UI components — One design system, consistent across all apps
  • Atomic commits — "Add user role field" commits the schema, API, web UI, and mobile UI in one PR
  • Unified tooling — One ESLint config, one TypeScript config, one CI pipeline

In 2026, monorepo tooling matured dramatically:

  • Turborepo (Vercel) — Build caching reduces CI time by 80%, simple config
  • Nx (Nrwl) — Enterprise-scale, affected command detection, dependency graph
  • pnpm workspaces — Consensus package manager for monorepos

Quick Comparison

StarterBuild ToolLanguageWebMobileAPI SharingBest For
T3 TurboTurborepoTypeScriptNext.jsExpotRPC (auto)Next.js + React Native
Turborepo starterTurborepoTS/JSAnyAnyManualCustom monorepo
Nx workspaceNxTS/JSAnyAnyManualEnterprise scale
SupastarterTurborepoTypeScriptNext.js/NuxttRPCFull SaaS monorepo

The Starters

T3 Turbo — Best Web + Mobile

Price: Free | Creator: Julius Marminge | GitHub Stars: 6k+

The canonical full-stack TypeScript monorepo. Next.js web app + Expo React Native mobile app, sharing tRPC API router, Prisma database schema, auth configuration, and UI components — all in one repository.

npm create t3-turbo@latest my-saas
cd my-saas && pnpm install && pnpm dev

Repository structure:

apps/
├── nextjs/           # Next.js web application
│   ├── app/          # App Router pages
│   └── package.json
└── expo/             # React Native mobile app
    ├── app/          # Expo Router pages
    └── package.json
packages/
├── api/              # tRPC router — shared by web and mobile
│   ├── src/router/
│   └── package.json
├── auth/             # NextAuth configuration
│   └── src/index.ts
├── db/               # Prisma schema and client
│   ├── prisma/schema.prisma
│   └── src/index.ts
├── ui/               # Shared React components
│   ├── src/button.tsx
│   └── package.json
└── validators/       # Zod schemas — shared validation
    └── src/index.ts

tooling/
├── eslint/           # Shared ESLint config
├── typescript/       # Shared tsconfig
└── tailwind/         # Shared Tailwind config

The type-sharing workflow:

// packages/db/prisma/schema.prisma — add field
model User {
  id    String  @id @default(cuid())
  name  String
  email String  @unique
  role  String  @default("user")  // NEW FIELD
}

// TypeScript error: run npx prisma generate
// packages/api/src/router/user.ts — update return type
const userRouter = router({
  me: protectedProcedure.query(({ ctx }) => {
    return ctx.db.user.findUnique({
      where: { id: ctx.session.user.id },
      // TypeScript now knows 'role' exists — select it
      select: { id: true, name: true, email: true, role: true },
    });
  }),
});

// apps/nextjs — TypeScript error: new 'role' field available
const { data: user } = api.user.me.useQuery();
// user.role is now typed as string

// apps/expo — same types, same errors
const { data: user } = api.user.me.useQuery();
// user.role typed identically

One schema change propagates through type errors to every client.

Choose if: You're building a web app + mobile app that share business logic from day one.

create-turbo — Official Turborepo Starter

Price: Free | Creator: Vercel | GitHub Stars: 30k+ (Turborepo)

The official minimal Turborepo starter. Two Next.js apps, a shared UI package, and shared ESLint/TypeScript configs. Framework-agnostic starting point for any Turborepo-based monorepo.

npx create-turbo@latest my-monorepo
# ✔ Which package manager? → pnpm
# ✔ What's your project name? → my-monorepo

# Structure:
# apps/web (Next.js)
# apps/docs (Next.js)
# packages/ui (React components)
# packages/eslint-config
# packages/typescript-config

Build pipeline config:

// turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "inputs": ["$TURBO_DEFAULT$"],
      "outputs": [".next/**", "!.next/cache/**", "dist/**"]
    },
    "lint": {
      "dependsOn": ["^lint"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

Turborepo build caching:

  • First run: builds everything (~2 minutes)
  • Subsequent runs: restores from cache if unchanged (~5 seconds)
  • Remote caching (Vercel): share cache between developers and CI

Choose if: You want the minimal Turborepo starting point to build your own monorepo on top of.

Nx Workspace — Best Enterprise Scale

Price: Free (Nx Cloud is $0-$40/month) | Creator: Nrwl | GitHub Stars: 24k+

Nx handles very large monorepos (1,000+ packages) that Turborepo struggles with. Features: affected command detection (only build/test packages changed by a commit), visual dependency graph, code generators, migration schematics, and first-class support for Angular, React, and Node.js.

npx create-nx-workspace@latest myorg --preset=ts

Affected command (Nx's killer feature):

# Only builds apps/packages affected by changes since main
nx affected --target=build --base=main

# Only runs tests for affected packages
nx affected --target=test --base=main

# Show what would be affected
nx show projects --affected --base=main

For a monorepo with 50 packages, running all tests takes 20 minutes. Running only affected tests takes 1-3 minutes. At 100+ packages, this becomes critical for CI speed.

Choose if: You're at enterprise scale (10+ developers, many packages) or need Nx's code generation and migration features.

Turborepo vs Nx: Feature Comparison

FeatureTurborepoNx
Build caching
Remote caching✅ Vercel✅ Nx Cloud
Affected detection⚠️ Basic✅ Advanced
Code generators
Dependency graph UI
Learning curveLowMedium
Config verbosityLowMedium
TypeScript supportNativeNative

For most SaaS projects: Turborepo. Simpler config, lower learning curve, sufficient for < 20 packages.

For large teams: Nx. Affected detection and code generators justify the added complexity.

pnpm Workspaces: The Foundation

Both Turborepo and Nx work on top of pnpm workspaces for package management:

# pnpm-workspace.yaml
packages:
  - "apps/*"
  - "packages/*"
  - "tooling/*"
// packages/ui/package.json
{
  "name": "@myorg/ui",
  "main": "./src/index.tsx",
  "exports": {
    ".": "./src/index.tsx"
  }
}
// apps/nextjs/package.json — import from workspace package
{
  "dependencies": {
    "@myorg/ui": "workspace:*",
    "@myorg/db": "workspace:*"
  }
}

Remote Caching: The CI Multiplier

Build caching is the highest-ROI configuration change after the initial monorepo setup. Local caching saves time on your machine; remote caching shares that cache across every developer on the team and every CI run.

How it works: Turborepo fingerprints each task's inputs — source files, environment variables, and lock file. If the fingerprint matches a cached result, it restores the output without re-running the task. On a typical pull request that touches only one app, remote caching restores builds for every unchanged package instantly.

For Turborepo, remote caching runs through Vercel. After running npx turbo login && npx turbo link, every turbo build run checks the remote cache first. The impact: a T3 Turbo CI pipeline that normally takes 3-4 minutes drops to 30-45 seconds on pull requests that touch only one app. The TURBO_TOKEN and TURBO_TEAM environment variables enable this in GitHub Actions with two extra lines in the CI configuration.

Nx Cloud provides the equivalent for Nx workspaces, with a generous free tier (unlimited agents, 6 months artifact retention). Nx's combination of affected detection — running only tests and builds for packages touched by a commit — plus remote caching makes it particularly effective at enterprise scale. A monorepo with 50 packages might have CI run all 50 package tests on every commit without these tools; with affected detection, only 3-4 packages run.

Shared Package Patterns

How you split packages determines whether the monorepo pays dividends or just adds ceremony. Three patterns work consistently well:

Shared validators: Zod schemas belong in a packages/validators package. The same schema validates API input server-side and form input client-side — one definition, no drift. T3 Turbo uses this pattern to share input validation across tRPC, web, and mobile.

Shared UI components: A packages/ui package with primitive components (Button, Input, Modal) built against your design tokens. App-specific components stay in each app; only truly reusable components move to the shared package. Resist the urge to put everything in packages/ui — the friction of cross-package imports discourages iteration on components that are still evolving.

Shared configuration: TypeScript config (tsconfig.base.json), ESLint config, and Tailwind config in tooling/ packages. Each app extends the shared configs rather than duplicating them. When you update a lint rule or TypeScript strict setting, it applies everywhere in one commit.

The pattern that fails consistently: treating packages/ as a domain layer where every domain entity (users, billing, products) becomes its own package. This creates a tightly coupled graph where every package imports from every other — which defeats the purpose of package isolation.

Key Takeaways

  • T3 Turbo is the best starting point for web + mobile TypeScript projects sharing business logic between Next.js and Expo
  • Turborepo's build caching reduces CI time by 60-80% — remote caching via Vercel shares cache between developers and CI for maximum effect
  • Nx handles enterprise-scale monorepos better than Turborepo, with affected detection and code generators justifying the added complexity at 10+ developers
  • A monorepo requires at least two apps with genuinely shared code to justify the setup overhead — don't add one speculatively for a single-app project
  • The three highest-value shared packages are validators (Zod schemas), UI primitives, and tooling config — domain logic packages create coupling that rarely pays off

When NOT to Use a Monorepo

Monorepos add real complexity. Common mistakes:

Premature monorepo: Solo developer with one Next.js app. The pnpm workspace, Turborepo config, and inter-package imports add hours of setup for zero benefit. Add a monorepo when you have an actual second app to share code with.

Over-splitting packages: Splitting a single app into 20 packages because it feels modular. Packages that import only from each other and have no external consumers don't need to be separate packages.

Using monorepo as a substitute for good module boundaries: A monorepo doesn't enforce good architecture. apps/web importing directly from apps/admin/internal/utils is still bad architecture regardless of Turborepo.

The right time for a monorepo: When you have two apps that genuinely share code (web + mobile, web + admin panel, multiple microsites with shared components) and the shared code changes frequently enough to benefit from atomic commits.


How to Evaluate Monorepo Starters

Not all Turborepo starters are built equally. Before committing to a monorepo boilerplate, evaluate:

Package boundary discipline. Look at the package.json files in each package. Do imports go in one direction (apps import packages, packages don't import apps)? Or do packages cross-import each other freely? Circular dependencies between packages defeat the purpose of package isolation and cause hard-to-debug build issues. A well-structured monorepo has a directed graph of dependencies with no cycles.

TypeScript path resolution. Monorepos have two ways to resolve cross-package imports: compiled output (build each package to dist/, import from dist/) or direct TypeScript source imports (import from src/ via TypeScript path aliases). Direct source imports provide faster feedback but require TypeScript project references configured correctly. Compiled output is more reliable but requires building packages before using them. Check which approach the starter uses and whether it's consistent.

CI pipeline configuration. A monorepo's CI pipeline should run lint, typecheck, and test only for packages affected by a given change. A pipeline that runs everything on every commit will slow to a crawl as the codebase grows. Check whether the starter's GitHub Actions or CI configuration uses --filter flags or Nx's affected detection.

Remote caching setup. Turborepo remote caching requires a TURBO_TOKEN and TURBO_TEAM environment variable pointing to Vercel. The starter should document this setup. Without remote caching, CI will rebuild every package from scratch on every run — eliminating the primary performance benefit of Turborepo.

What These Monorepo Starters Have in Common

Despite their different target audiences, T3 Turbo, the official Turborepo starter, and Nx workspaces share structural patterns that reflect hard-won lessons about monorepo maintenance:

All use pnpm workspaces as the package manager — pnpm's workspace protocol prevents hoisting ambiguity and phantom dependency issues that npm and yarn workspaces allow. All keep framework-specific tooling (Next.js, Expo) in apps/, while shared code lives in packages/. All configure TypeScript with a shared base config in tooling/ that individual packages extend, preventing config drift. All define build pipelines in their orchestration tool's config file rather than per-package npm scripts, which enables caching and dependency tracking.

The structural similarity means the mental model transfers across starters. Learn the T3 Turbo pattern once and you can read any Turborepo-based monorepo without confusion.

Migrating a Single-App Project to a Monorepo

Teams often reach the "we need a monorepo" inflection point when a second app (a mobile client, an admin dashboard, a separate marketing site) needs to share code with the first app. The migration path from a single Next.js app to a T3 Turbo-style monorepo has predictable steps:

Move the existing app to apps/web/. Extract shared types and utilities into packages/core/. Extract shared UI components into packages/ui/. Move database schema and client to packages/db/. Add pnpm workspace configuration and a turbo.json with task definitions. Update CI to use pnpm turbo build instead of npm run build.

The migration typically takes 2-4 days for a medium-sized codebase. The main risk: import resolution breaks when files move between packages. Run tsc --noEmit after each package extraction to catch broken imports early.

For the full comparison of boilerplates that use monorepo architecture as their structural foundation, see best boilerplate with monorepo architecture covering Supastarter, Bedrock, and T3 Turbo in depth. For T3 Stack variants specifically, the T3 Stack variations guide compares T3 Turbo with the single-app T3 configurations. The production SaaS with free tools guide covers how to build a full monorepo SaaS using entirely free-tier services.

Compare monorepo and single-app SaaS boilerplates in the StarterPick directory.

See our full T3 Turbo review and how it compares to T3 Stack variations.

Review best boilerplates with monorepo architecture — how Supastarter, Bedrock, and T3 Turbo structure packages.

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.