Skip to main content

Guide

Best Boilerplate with Monorepo Architecture 2026

Monorepos enable shared code, consistent tooling, and multi-app development. Compare how T3 Turbo, Supastarter, and Bedrock structure their Turborepo in 2026.

StarterPick Team

TL;DR

Supastarter has the best monorepo architecture — isolated packages for auth, billing, email, UI, and i18n that can be independently developed and tested. T3 Turbo provides a free monorepo foundation with web+mobile sharing. Bedrock targets enterprise with background jobs, monitoring, and strict package boundaries. Choose a monorepo boilerplate only when you have concrete multi-app requirements — the added complexity is real and meaningful.

Monorepo: When It Helps and When It Doesn't

As SaaS products grow, they accumulate multiple concerns: the web app, admin panel, marketing site, shared UI components, email templates, database schemas, and utility libraries. A monorepo keeps these in one repository with shared tooling and dependencies — but it adds real setup complexity that single-app boilerplates don't require.

Three boilerplates use Turborepo monorepos as their foundation: T3 Turbo (free, community-maintained), Supastarter ($299-$349, commercial), and Bedrock ($395-$995, enterprise). Each structures the monorepo differently based on its target audience and use case.

Quick Comparison

BoilerplatePriceMonorepo ToolWeb + MobilePackage CountBest For
T3 TurboFreeTurborepo✅ Next.js + Expo5Web + mobile
Supastarter$299-$349Turborepo❌ Web only8+SaaS product
Bedrock$395-$995Turborepo❌ Web only10+Enterprise

Monorepo Structure Comparison

T3 Turbo

apps/
├── nextjs/        # Web app (Next.js + tRPC client)
└── expo/          # React Native mobile app

packages/
├── api/           # tRPC router (shared by web + mobile)
├── auth/          # NextAuth.js configuration
├── db/            # Prisma schema and client
├── ui/            # Shared React components
└── validators/    # Zod schemas

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

T3 Turbo's minimal package count prioritizes simplicity over isolation. The api package is the core — it contains the tRPC router shared between the Next.js web app and Expo mobile app. When you add a field to a tRPC procedure, TypeScript errors appear in both clients simultaneously. This forces atomic updates and eliminates the "works on web, forgot mobile" class of bugs.

Turborepo pipeline configuration:

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

The ^build means "build all packages this depends on first." Running turbo dev starts the web app, Expo bundler, and shared package watchers concurrently.

Supastarter

apps/
├── web/           # Main Next.js (or Nuxt) application

packages/
├── core/          # Shared types, utilities, constants
├── ui/            # shadcn/ui + custom components
├── auth/          # Authentication (Auth.js or Lucia)
├── billing/       # Payment providers
│   ├── stripe/    # Stripe implementation
│   └── lemon-squeezy/  # LemonSqueezy implementation
├── email/         # Email templates + sending
├── i18n/          # Translations + locale config
├── database/      # Prisma schema + client
└── config/        # Shared configuration

Supastarter's billing package is uniquely structured: each payment provider is a sub-package implementing a shared interface. Swapping from Stripe to Polar means replacing one package's implementation, not rewriting billing logic throughout the application.

// packages/billing/src/types.ts — shared interface
export interface BillingProvider {
  createCheckoutSession(params: CheckoutParams): Promise<CheckoutSession>;
  cancelSubscription(subscriptionId: string): Promise<void>;
  getPortalUrl(customerId: string): Promise<string>;
}

// packages/billing/stripe/src/index.ts — Stripe implementation
export const stripeProvider: BillingProvider = {
  async createCheckoutSession(params) {
    const session = await stripe.checkout.sessions.create({
      mode: 'subscription',
      line_items: [{ price: params.priceId, quantity: 1 }],
      success_url: params.successUrl,
      cancel_url: params.cancelUrl,
    });
    return { url: session.url! };
  },
  // ...
};

This provider-swappable architecture is Supastarter's strongest feature. Auth, billing, and email are all structured to be replaceable without changing the application code that calls them.

Bedrock

apps/
├── web/           # Main Next.js application
├── worker/        # Background job worker (separate process)
└── api/           # Optional separate API service

packages/
├── core/          # Domain models, business logic
├── ui/            # Component library
├── auth/          # Authentication + authorization
├── billing/       # Stripe integration
├── email/         # Email templates
├── jobs/          # Background job definitions
├── monitoring/    # Logging, metrics, health checks
└── testing/       # Test utilities, factories

Bedrock separates the background job worker into its own deployable application. The jobs/ package defines job types and schemas; the worker/ app imports and executes them. This means you can scale the worker independently from the web app.

// packages/jobs/src/send-welcome-email.ts
import { createJob } from '@myapp/jobs';

export const sendWelcomeEmailJob = createJob({
  name: 'send-welcome-email',
  schema: z.object({ userId: z.string() }),
  handler: async ({ userId }) => {
    const user = await db.user.findUnique({ where: { id: userId } });
    await sendEmail({ to: user.email, template: 'welcome', data: user });
  },
});

// apps/web/src/api/auth/route.ts — enqueue from web app
await sendWelcomeEmailJob.enqueue({ userId: newUser.id });
// apps/worker/ — executed by the worker process

Best for: Enterprise SaaS with background jobs, monitoring requirements, and team-based ownership of packages.

Turborepo Build Caching

All three monorepos use Turborepo for build orchestration. Build caching is the key performance feature:

# First run: builds everything
pnpm turbo build  # ~3 minutes

# Second run (nothing changed): restores from cache
pnpm turbo build  # ~3 seconds — 98% faster

# Remote caching (Vercel): share cache between developers and CI
npx turbo login && npx turbo link

# CI: only build what changed since main
pnpm turbo build --filter=...[main]

Remote caching means your CI pipeline benefits from a developer's local build. If you built packages/ui locally and CI runs the same commit hash, Turborepo restores from the remote cache instead of rebuilding.

Cross-Package TypeScript Configuration

All three monorepos share TypeScript configuration across packages:

// tooling/typescript/base.json
{
  "$schema": "https://json.schemastore.org/tsconfig",
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["ES2022"],
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "exactOptionalPropertyTypes": true,
    "noUncheckedIndexedAccess": true
  }
}

// packages/ui/tsconfig.json — extends shared config
{
  "extends": "@myapp/tsconfig/base.json",
  "compilerOptions": {
    "jsx": "react-jsx",
    "lib": ["ES2022", "DOM"]
  }
}

One TypeScript configuration update propagates to every package. This eliminates the "works on this package, breaks on that one" issue that comes from per-package TypeScript config drift.

pnpm Workspaces Setup

All three monorepos use pnpm workspaces:

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

The workspace:* version resolves to the local package, not an npm registry package. Changes to packages/ui are immediately reflected in apps/web without publishing.

Day-to-Day Development in a Monorepo

The theoretical benefits of monorepos become concrete during development. Here's what each scenario looks like in practice:

Adding a new database field across T3 Turbo:

  1. Edit packages/db/prisma/schema.prisma — add the field
  2. Run npx prisma generate — regenerates the typed client
  3. TypeScript errors appear immediately in packages/api/ and both apps
  4. Fix the errors, run pnpm turbo db:push, done

A single schema change propagates through type errors to every consumer. No "we forgot to update the mobile API" bugs.

Adding a new UI component across Supastarter:

  1. Create the component in packages/ui/src/components/my-component.tsx
  2. Export it from packages/ui/src/index.tsx
  3. Import it in apps/web — TypeScript infers types from the package immediately
  4. Update the docs or admin apps in the same commit

One PR, one review, one merge. The component is available everywhere simultaneously.

Adding a background job in Bedrock:

  1. Define the job schema in packages/jobs/src/my-job.ts
  2. Add the handler logic
  3. Enqueue it from apps/web wherever the trigger event occurs
  4. The apps/worker process picks it up — no additional deployment configuration

The monorepo enforces architectural discipline: the web app can't accidentally run background jobs (it only enqueues), and the worker can't serve HTTP (it only processes).

Deployment Configuration

Each boilerplate in a monorepo deploys differently because apps and packages have different deployment targets:

T3 Turbo deployment:

  • apps/nextjs → Vercel (or any Next.js host)
  • apps/expo → App Store / Google Play via EAS Build
  • packages/* → not deployed independently, bundled into apps at build time

Supastarter deployment:

  • apps/web → Vercel (or Railway for more control)
  • packages/* → bundled into the web app
  • Database → Neon PostgreSQL or Supabase

Bedrock deployment:

  • apps/web → Vercel or Railway
  • apps/worker → Railway or Fly.io (always-on process)
  • packages/* → bundled into respective apps
  • Database + queues → Railway or Fly.io managed services

The worker in Bedrock is the notable difference: it runs as a persistent process, not a serverless function. Background jobs that take longer than 30 seconds (image processing, data exports, email campaigns) require an always-on worker rather than a Vercel serverless function.

Monorepo vs Polyrepo: The Real Trade-offs

The alternative to a monorepo is a polyrepo: separate git repositories for each app, with shared packages published to npm (or a private registry like GitHub Packages or Verdaccio).

Polyrepo advantages:

  • Independent deployment and versioning per app
  • Smaller, faster git operations per repo
  • Clearer ownership when different teams own different apps
  • No Turborepo or workspace configuration required

Monorepo advantages:

  • Atomic commits across multiple apps ("add invoice feature" in one PR)
  • No publish-wait-install cycle when updating shared packages
  • Shared tooling configuration with a single source of truth
  • TypeScript type errors appear immediately across all consumers

The right choice depends on team structure. For a 1-5 person team building one product, a monorepo's atomic commit advantage is significant. For a large engineering organization where separate teams own separate services, polyrepos with a private npm registry are often better — the overhead of the monorepo (build cache configuration, affected detection, workspace protocol) isn't worth it when teams work independently.

All three boilerplates (T3 Turbo, Supastarter, Bedrock) commit to the monorepo model. If you choose any of them, you're choosing Turborepo's model for structuring your codebase.

When to Choose a Monorepo Boilerplate

Monorepos add real tooling complexity: pnpm workspace configuration, Turborepo pipeline setup, package versioning, and cross-package TypeScript references add 1-3 days of initial setup that a single-app boilerplate doesn't require.

Choose Supastarter when:

  • You need provider-swappable billing, auth, or email packages
  • Clean architecture and package isolation matter from day one
  • You want a production-grade monorepo without building the structure yourself

Choose T3 Turbo when:

  • You're building both a web app and React Native mobile app
  • Free and open source is a requirement
  • Community patterns and examples matter more than polish

Choose Bedrock when:

  • Enterprise requirements: background jobs, monitoring, strict package boundaries
  • Team-based development where different packages have clear ownership
  • CI/CD optimization and build pipeline control are priorities

Choose a single-app boilerplate (ShipFast, T3 Stack) when:

  • Solo developer or small team without multi-app requirements
  • Fast initial deployment matters more than long-term architecture
  • You don't have a concrete second app to share code with yet

The right time to use a monorepo: when you have two apps that genuinely share code (web + mobile, web + admin, multiple microsites with shared design system) and that shared code changes frequently enough to benefit from atomic commits. Not speculatively.


How to Evaluate Monorepo Boilerplates

The selection criteria for a monorepo boilerplate differ from single-app starters. Beyond the standard evaluation of auth, billing, and UI quality, evaluate:

Package boundary enforcement. Does the monorepo enforce import direction? Apps should import from packages, not the other way around. Packages shouldn't import from other packages unless that dependency is explicitly declared in package.json. A monorepo that allows apps/web to import directly from apps/admin creates coupling that the monorepo architecture was supposed to prevent. Check the ESLint configuration for import rules that enforce package boundaries.

Build pipeline correctness under partial changes. Clone the repo, make a change only to packages/ui, and run pnpm turbo build. Only packages/ui and the apps that depend on it should rebuild. If the entire monorepo rebuilds, the turbo.json task configuration is incorrect. This is the difference between a demo-quality Turborepo setup and one that saves meaningful CI time in practice.

Cross-package TypeScript references. TypeScript project references allow the TypeScript language server to provide accurate completion and error detection across packages without building every package first. Check whether the monorepo's tsconfig.json files use references to point to dependent packages. Without project references, type errors in a dependency package may not surface as errors in consuming packages until the next build.

Development server startup. In a well-configured Turborepo monorepo, pnpm turbo dev starts all development servers concurrently — the Next.js web app, the Expo bundler, the shared package watchers. If startup requires manual steps (run this command, wait for it, then run another command), the dev pipeline configuration needs work.

When Monorepo Architecture Pays Off Most

Monorepo architecture generates the highest returns in specific development scenarios:

High schema change velocity. Products in early stages that change their data model frequently benefit from monorepos most. Every schema change is a forcing function that updates all clients atomically. Without a monorepo, the same change requires coordinated PRs across multiple repositories with the risk of drift.

Design system consistency at scale. Products with multiple apps (web, mobile, admin, marketing) that need visual consistency benefit from a shared packages/ui. When the design team updates a button style, one PR propagates the change to all four apps. This consistency would require coordinated cross-repository changes in a polyrepo setup.

Shared validation logic. Zod schemas in packages/validators that define both API input validation (server-side) and form validation (client-side) eliminate the most common cause of frontend-backend contract drift. The form literally cannot submit a value that the API won't accept — the validation schema is the same object.

The monorepo architecture earns its complexity when at least two of these scenarios are active. For a single-app product with no near-term second app, a monorepo's setup overhead (pnpm workspaces, Turborepo config, cross-package TypeScript) is overhead without offsetting benefit.

For the community monorepo starters (T3 Turbo, official Turborepo starter, Nx workspace) and how they compare on build tooling and CI performance, see the best monorepo boilerplates guide. For T3 Turbo specifically and its role in the T3 Stack ecosystem, see the T3 Stack variations guide. For commercial SaaS boilerplates and how they handle the architecture decision, the best SaaS boilerplates guide covers Supastarter and Bedrock's approaches.

The monorepo decision is ultimately about whether the overhead of Turborepo configuration, workspace protocol dependencies, and cross-package TypeScript references is justified by the benefits of atomic commits and shared tooling. For most teams building their first SaaS product, the answer is no. For teams with two deployed apps sharing business logic — a web app and a mobile app, a web app and an admin dashboard, a consumer product and a developer API — the answer becomes yes fairly quickly. Make the decision based on what you have today, not on what you might build in two years.

The boilerplate and tool choices covered here represent the most actively maintained options in their category as of 2026. Evaluate each against your specific requirements: team expertise, deployment infrastructure, budget, and the features your product requires on day one versus those you can add incrementally. The best starting point is the one that lets your team ship the first version of your product fastest, with the least architectural debt.

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

See our guide to monorepo boilerplates — Turborepo vs Nx for large-scale multi-app development.

Review T3 Stack variations — including T3 Turbo, the free monorepo boilerplate for web + mobile.

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.