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
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
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"
}
}
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"]
}
}
}
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": "*"
}
}
// 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>
)
}
// apps/web/app/page.tsx
import { Button } from '@repo/ui/button' // shared component
Shared Database Package
// packages/database/package.json
{
"name": "@repo/database",
"exports": {
".": "./index.ts"
}
}
// 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
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
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)