| Feature | Turborepo | Nx |
|---|---|---|
| Setup Complexity | Simple, minimal config | More configuration options |
| Build Caching | Excellent, hash-based | Excellent, computation caching |
| Affected Commands | --filter flag | Built-in affected:* |
| Remote Caching | Vercel (free tier) | Nx Cloud (free tier) |
| Best For | Getting started, medium teams | Large orgs, enterprise needs |
my-monorepo/
├── apps/
│ ├── web/ # Next.js frontend
│ ├── api/ # Express/Fastify backend
│ └── admin/ # Admin dashboard
├── packages/
│ ├── ui/ # Shared React components
│ ├── shared/ # Shared types and utilities
│ ├── database/ # Prisma schema and client
│ └── config/ # Shared configs (tsconfig, eslint)
├── package.json
├── pnpm-workspace.yaml
├── turbo.json
└── tsconfig.jsonpackages:
- 'apps/*'
- 'packages/*'{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/", ".next/"]
},
"dev": {
"cache": false,
"persistent": true
},
"test": {
"dependsOn": ["build"],
"outputs": ["coverage/"]
},
"lint": {
"outputs": []
}
}
}{
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"moduleResolution": "bundler",
"target": "ES2022",
"lib": ["ES2022"]
}
}{
"extends": "@myorg/config/tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"jsx": "react-jsx"
},
"include": ["src//*"],
"exclude": ["node_modules", "dist"]
}{
"name": "@myorg/shared",
"version": "0.0.0",
"private": true,
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"scripts": {
"build": "tsc",
"dev": "tsc --watch"
}
}// Domain types shared across apps
export interface User {
id: string
email: string
name: string
role: 'admin' | 'user' | 'viewer'
}
export interface ApiResponse {
data: T
meta: {
requestId: string
timestamp: number
}
}
// Utility types
export type Nullable = T | null
export type AsyncFunction = () => Promise {
"dependencies": {
"@myorg/shared": "workspace:*",
"@myorg/ui": "workspace:*"
}
}# Build only packages affected since last commit
turbo run build --filter=[HEAD^1]
# Build only packages affected compared to main
turbo run build --filter=[origin/main...]
# Build specific package and its dependencies
turbo run build --filter=@myorg/web...name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2 # Needed for affected commands
- uses: pnpm/action-setup@v3
with:
version: 8
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
# Remote caching with Vercel
- run: pnpm turbo build test lint --filter=[HEAD^1]
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}- Keep packages small and focused—single responsibility
- Use workspace:* for internal dependencies
- Share ESLint/Prettier configs via internal package
- Set up remote caching from day one
- Use TypeScript project references for faster type checking
- Document package purposes in README files
Need Help Setting Up Your Monorepo?
We help teams architect and implement TypeScript monorepos that scale—from initial setup to CI/CD optimization.
Discuss Your Architecture →
