Skip to main content
โšก Calmops

Monorepo with Turborepo: Scale Your Startup Codebase 2026

Introduction

As your startup grows, managing multiple projects (web app, mobile app, backend API, shared libraries) becomes challenging. A monorepo with Turborepo lets you share code between projects while maintaining independent deployments and fast builds. This guide shows you how to set it up.


Why Monorepo?

The Problem with Multiple Repos

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚            Multiple Repos โ†’ Chaos                            โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                             โ”‚
โ”‚  repo-web/                                                 โ”‚
โ”‚    โ””โ”€โ”€ utils/                                              โ”‚
โ”‚        โ””โ”€โ”€ formatDate.ts (v1.0)                           โ”‚
โ”‚                                                             โ”‚
โ”‚  repo-mobile/                                              โ”‚
โ”‚    โ””โ”€โ”€ utils/                                              โ”‚
โ”‚        โ””โ”€โ”€ formatDate.ts (v0.9) โ† Out of sync!            โ”‚
โ”‚                                                             โ”‚
โ”‚  repo-api/                                                 โ”‚
โ”‚    โ””โ”€โ”€ utils/                                              โ”‚
โ”‚        โ””โ”€โ”€ formatDate.ts (v1.0)                           โ”‚
โ”‚                                                             โ”‚
โ”‚  โ†’ Bug fixes don't propagate                              โ”‚
โ”‚  โ†’ Inconsistent code                                      โ”‚
โ”‚  โ†’ Hard to refactor                                       โ”‚
โ”‚                                                             โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The Monorepo Solution

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚              Monorepo โ†’ Organized                           โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                             โ”‚
โ”‚  my-startup/                                               โ”‚
โ”‚    โ”œโ”€โ”€ apps/                                               โ”‚
โ”‚    โ”‚   โ”œโ”€โ”€ web/           โ† Next.js                        โ”‚
โ”‚    โ”‚   โ”œโ”€โ”€ mobile/        โ† React Native                   โ”‚
โ”‚    โ”‚   โ”œโ”€โ”€ api/           โ† Express/Next.js API             โ”‚
โ”‚    โ”‚   โ””โ”€โ”€ admin/         โ† Admin dashboard                 โ”‚
โ”‚    โ”‚                                                      โ”‚
โ”‚    โ””โ”€โ”€ packages/                                           โ”‚
โ”‚        โ”œโ”€โ”€ ui/            โ† Shared UI components           โ”‚
โ”‚        โ”œโ”€โ”€ utils/         โ† Shared utilities               โ”‚
โ”‚        โ”œโ”€โ”€ config/        โ† Shared configs                 โ”‚
โ”‚        โ””โ”€โ”€ types/         โ† Shared TypeScript types        โ”‚
โ”‚                                                             โ”‚
โ”‚  โ†’ One source of truth                                     โ”‚
โ”‚  โ†’ Easy refactoring                                        โ”‚
โ”‚  โ†’ Shared dependencies                                     โ”‚
โ”‚                                                             โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Setting Up Turborepo

Initial Setup

# Create new monorepo
mkdir my-startup && cd my-startup

# Initialize
npm init -w

# Install turbo
npm install -D turbo

# Create turbo.json
echo '{"pipeline": {"build": {}, "dev": {}, "lint": {}}}' > turbo.json

Package Structure

{
  "name": "my-startup",
  "private": true,
  "workspaces": [
    "apps/*",
    "packages/*"
  ],
  "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev",
    "lint": "turbo run lint"
  }
}
// apps/web/package.json
{
  "name": "@my-startup/web",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "lint": "next lint"
  },
  "dependencies": {
    "@my-startup/ui": "*",
    "@my-startup/utils": "*",
    "next": "latest",
    "react": "latest"
  }
}

Turbo Configuration

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

Sharing Code Between Apps

Shared Utils Package

// packages/utils/src/index.ts
export function formatDate(date: Date): string {
  return new Intl.DateTimeFormat('en-US', {
    year: 'numeric',
    month: 'long',
    day: 'numeric'
  }).format(date)
}

export function cn(...classes: (string | undefined | null | false)[]): string {
  return classes.filter(Boolean).join(' ')
}

export function truncate(str: string, length: number): string {
  if (str.length <= length) return str
  return str.slice(0, length) + '...'
}
// packages/utils/package.json
{
  "name": "@my-startup/utils",
  "version": "0.0.0",
  "main": "./src/index.ts",
  "types": "./src/index.ts",
  "exports": {
    ".": "./src/index.ts",
    "./date": "./src/date.ts",
    "./string": "./src/string.ts"
  }
}

Shared UI Components

// packages/ui/src/Button.tsx
import { cn } from '@my-startup/utils'

interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: 'primary' | 'secondary' | 'ghost'
}

export function Button({ 
  variant = 'primary', 
  className,
  children,
  ...props 
}: ButtonProps) {
  return (
    <button 
      className={cn(
        'px-4 py-2 rounded-lg font-medium transition-colors',
        variant === 'primary' && 'bg-blue-600 text-white hover:bg-blue-700',
        variant === 'secondary' && 'bg-gray-100 text-gray-900 hover:bg-gray-200',
        variant === 'ghost' && 'bg-transparent hover:bg-gray-100',
        className
      )}
      {...props}
    >
      {children}
    </button>
  )
}

Using Shared Packages

// apps/web/app/page.tsx
import { Button } from '@my-startup/ui'
import { formatDate, cn } from '@my-startup/utils'

export default function HomePage() {
  return (
    <div className={cn('p-8', 'max-w-2xl')}>
      <h1 className="text-3xl font-bold">
        Welcome {formatDate(new Date())}
      </h1>
      
      <Button variant="primary" onClick={() => console.log('clicked')}>
        Get Started
      </Button>
    </div>
  )
}

Build Optimization

Caching

// turbo.json - enable remote caching
{
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**"],
      "cache": true
    }
  }
}
# Login to Vercel for remote cache (optional)
npx turbo login
npx turbo link

Parallel Execution

# Run multiple apps in parallel
turbo run dev --parallel

# Run specific app
turbo run dev --filter=web

# Run all except mobile
turbo run dev --filter=!mobile

Build Scripts

// root package.json
{
  "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev --parallel",
    "lint": "turbo run lint",
    "test": "turbo run test",
    "clean": "turbo run clean",
    "typecheck": "turbo run typecheck"
  }
}

Sharing Types

API Types

// packages/types/src/api.ts
export interface User {
  id: string
  email: string
  name: string
  createdAt: string
}

export interface CreateUserInput {
  email: string
  name: string
  password: string
}

export interface ApiResponse<T> {
  data?: T
  error?: string
}
// Use in API (apps/api/src/routes/users.ts)
import { User, CreateUserInput, ApiResponse } from '@my-startup/types'

export async function createUser(input: CreateUserInput): Promise<ApiResponse<User>> {
  // Implementation
}

// Use in Web (apps/web/app/users/page.tsx)
import { User, ApiResponse } from '@my-startup/types'

// Full type safety across apps!

Deployment

Deploying Individual Apps

# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy-web:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
      - run: npm install
      - run: npm run build
        working-directory: apps/web
      - uses: amondnet/vercel-action@v25
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
          vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
          vercel-args: '--prod'
          working-directory: apps/web

Environment Variables

# .env
# Root - shared vars
DATABASE_URL=postgres://...

# App-specific in .env.{app}.local
# apps/web/.env.local
NEXT_PUBLIC_API_URL=http://localhost:3000

Common Pitfalls

1. Too Many Shared Packages

Wrong:

# Each tiny utility in its own package
packages/
  utils/
    date/
    string/
    array/
    object/
    number/
# Result: Over-engineered, slow installs

Correct:

# Group related code
packages/
  utils/        # All utilities
  ui/           # All UI components
  config/       # All configs
# Start simple, extract when needed

2. Not Using Filters

Wrong:

# Rebuild everything for small change
npm run build

Correct:

# Rebuild only affected apps
turbo run build --filter=web

Key Takeaways

  • Start monorepo early - Easier than migrating later
  • Turborepo handles caching - Super fast builds
  • Share types between apps - End-to-end type safety
  • Deploy apps independently - Each app has its own CI/CD
  • Keep it simple - Don’t over-engineer package structure

External Resources

Documentation

Examples

Comments