DEV Community

Atlas Whoff
Atlas Whoff

Posted on

Turborepo Monorepo Setup: Shared Packages, Build Caching, and CI Configuration

Monorepos let you share code between apps without the overhead of separate npm packages. Turborepo makes them fast by caching build outputs and running tasks in parallel. Here's how to set one up that actually scales.

When a Monorepo Makes Sense

Use a monorepo when you have:

  • A web app and a mobile app sharing types and utilities
  • Multiple microservices sharing database clients or auth logic
  • A design system used across multiple apps
  • An API and its SDK that must stay in sync

Don't use a monorepo for unrelated projects. The overhead isn't worth it.

Initial Setup

npx create-turbo@latest my-monorepo
cd my-monorepo
Enter fullscreen mode Exit fullscreen mode

Structure:

my-monorepo/
  apps/
    web/          -- Next.js app
    docs/         -- Documentation site
    api/          -- Express/Fastify API
  packages/
    ui/           -- Shared React components
    database/     -- Prisma client + schema
    config/       -- Shared tsconfig, eslint config
    types/        -- Shared TypeScript types
  package.json    -- Root workspace config
  turbo.json      -- Task pipeline config
Enter fullscreen mode Exit fullscreen mode

Root package.json

{
  "name": "my-monorepo",
  "private": true,
  "workspaces": ["apps/*", "packages/*"],
  "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev --parallel",
    "test": "turbo run test",
    "lint": "turbo run lint",
    "typecheck": "turbo run typecheck"
  },
  "devDependencies": {
    "turbo": "latest"
  }
}
Enter fullscreen mode Exit fullscreen mode

Turbo Pipeline Configuration

// turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],  // build deps first
      "outputs": [".next/**", "dist/**"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    },
    "test": {
      "dependsOn": ["build"],
      "outputs": ["coverage/**"]
    },
    "lint": {
      "outputs": []
    },
    "typecheck": {
      "dependsOn": ["^build"]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Shared UI Package

// packages/ui/package.json
{
  "name": "@repo/ui",
  "version": "0.0.1",
  "exports": {
    "./button": {
      "import": "./src/button.tsx",
      "types": "./src/button.tsx"
    },
    "./card": "./src/card.tsx"
  },
  "peerDependencies": {
    "react": "*",
    "react-dom": "*"
  }
}
Enter fullscreen mode Exit fullscreen mode
// packages/ui/src/button.tsx
import { cn } from './utils'

export function Button({ className, children, ...props }) {
  return (
    <button className={cn('px-4 py-2 rounded bg-blue-600 text-white', className)} {...props}>
      {children}
    </button>
  )
}
Enter fullscreen mode Exit fullscreen mode
// apps/web/app/page.tsx
import { Button } from '@repo/ui/button'  // shared component
Enter fullscreen mode Exit fullscreen mode

Shared Database Package

// packages/database/package.json
{
  "name": "@repo/database",
  "exports": {
    ".": "./index.ts"
  }
}
Enter fullscreen mode Exit fullscreen mode
// packages/database/index.ts
export { PrismaClient } from '@prisma/client'
export * from '@prisma/client'  // re-export all types

// Singleton
import { PrismaClient } from '@prisma/client'

const globalForPrisma = globalThis as { prisma?: PrismaClient }
export const db = globalForPrisma.prisma ?? new PrismaClient()
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = db
Enter fullscreen mode Exit fullscreen mode

Remote Caching

Share build cache across your team and CI:

# Authenticate with Vercel (free)
npx turbo login
npx turbo link

# Or self-host with Turborepo Remote Cache
Enter fullscreen mode Exit fullscreen mode

With remote cache, a CI run that was already built by a teammate takes seconds instead of minutes.


The Ship Fast Skill Pack at whoffagents.com includes a /deploy skill that handles monorepo CI/CD configuration for Turborepo + Vercel or self-hosted. $49 one-time.

Top comments (0)