TypeScript Monorepo with Turborepo
Why Turborepo?
Problems with large monorepos:
- Long build times (rebuilding unchanged code)
- Complex task dependencies (build A before B)
- No sharing build caches
Turborepo solves:
- Incremental builds (only rebuild changed packages)
- Remote caching (share cache across CI runs)
- Parallel execution with dependency awareness
Setup
# Create new monorepo
npx create-turbo@latest my-monorepo
cd my-monorepo
# Or add to existing
npm install -D turbo
Structure
monorepo/
├── apps/
│ ├── web/ (Next.js)
│ ├── api/ (Express/Fastify)
│ └── mobile/ (React Native)
├── packages/
│ ├── ui/ (shared React components)
│ ├── utils/ (shared utilities)
│ ├── types/ (shared TypeScript types)
│ └── config/
│ ├── eslint/
│ └── tsconfig/
├── turbo.json
└── package.json (root)
turbo.json Configuration
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": [".env.local"],
"pipeline": {
"build": {
"dependsOn": ["^build"], // Build dependencies first
"outputs": ["dist/**", ".next/**"],
"cache": true
},
"test": {
"dependsOn": ["^build"],
"outputs": ["coverage/**"],
"cache": true
},
"lint": {
"outputs": []
},
"dev": {
"cache": false,
"persistent": true // Long-running process
},
"type-check": {
"outputs": []
}
}
}
Shared Package Example
// packages/ui/src/Button.tsx
import React from 'react';
export interface ButtonProps {
variant?: 'primary' | 'secondary';
children: React.ReactNode;
onClick?: () => void;
disabled?: boolean;
}
export function Button({ variant = 'primary', children, onClick, disabled }: ButtonProps) {
return (
<button
className={`btn btn-${variant}`}
onClick={onClick}
disabled={disabled}
>
{children}
</button>
);
}
// packages/ui/package.json
{
"name": "@myapp/ui",
"version": "0.0.0",
"main": "./src/index.ts",
"types": "./src/index.ts",
"exports": {
".": "./src/index.ts"
}
}
// apps/web/package.json
{
"dependencies": {
"@myapp/ui": "*"
}
}
Remote Caching
# Login to Turbo Remote Cache
npx turbo login
# Link to remote cache
npx turbo link
# Or use Vercel Remote Cache
# Or self-host with turbo-remote-cache package
# Build with remote cache
turbo build --remote-only
Task Commands
# Run all builds (parallel, with caching)
turbo build
# Run specific app's dev server
turbo dev --filter=web
# Run tests for changed packages only
turbo test --filter=[HEAD^1]
# Run in all packages matching pattern
turbo build --filter=./packages/*
# Dry run (show what would run)
turbo build --dry
# Force rebuild (ignore cache)
turbo build --force
Shared TypeScript Config
// packages/config/tsconfig/base.json
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022"],
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"skipLibCheck": true,
"isolatedModules": true,
"resolveJsonModule": true
}
}
// apps/web/tsconfig.json
{
"extends": "@myapp/config/tsconfig/nextjs.json",
"include": [".", "next-env.d.ts"],
"exclude": ["node_modules"]
}
Turborepo's incremental builds can reduce CI times by 80%+ once the remote cache is warm.