
Turborepo Monorepo Guide: Organize Your Frontend Projects at Scale
Turborepo's intelligent caching makes monorepos fast. Learn to set up a Turborepo workspace with shared packages, understand task graphs, and optimize CI build times.
Why Turborepo for Monorepos
A monorepo puts multiple projects in one repository — your app, a shared UI component library, utility packages, and internal tools. Without intelligent caching, every CI run rebuilds everything. Turborepo's content-aware caching means unchanged packages are never rebuilt — dramatically cutting CI times from 10+ minutes to under 2.
Initial Setup
npx create-turbo@latest my-monorepo
cd my-monorepo
This creates a workspace with example apps and packages. The structure:
my-monorepo/
apps/
web/ # Your Next.js app
docs/ # Documentation site
packages/
ui/ # Shared React components
config-ts/ # Shared tsconfig
config-eslint/ # Shared ESLint config
package.json # Root workspace config
turbo.json # Pipeline definition
pnpm-workspace.yaml
Defining the Pipeline in turbo.json
// turbo.json
{
"$schema": "https://turbo.build/schema.json",
"ui": "tui",
"tasks": {
"build": {
"dependsOn": ["^build"],
"inputs": ["$TURBO_DEFAULT$", ".env.local"],
"outputs": [".next/**", "!.next/cache/**", "dist/**"]
},
"test": {
"dependsOn": ["^build"],
"inputs": ["src/**", "tests/**", "vitest.config.*"]
},
"lint": {
"inputs": ["src/**", "*.json"]
},
"typecheck": {
"dependsOn": ["^build"]
},
"dev": {
"cache": false,
"persistent": true
}
}
}
Creating a Shared UI Package
// packages/ui/package.json
{
"name": "@repo/ui",
"version": "0.0.0",
"private": true,
"exports": {
".": "./src/index.ts",
"./button": "./src/button.tsx",
"./card": "./src/card.tsx"
},
"devDependencies": {
"@repo/config-ts": "workspace:*",
"typescript": "^5.4.2"
},
"peerDependencies": {
"react": "^19.0.0"
}
}
// packages/ui/src/button.tsx
import { type ButtonHTMLAttributes } from 'react';
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'ghost';
size?: 'sm' | 'md' | 'lg';
}
export function Button({ variant = 'primary', size = 'md', className = '', ...props }: ButtonProps) {
const variants = {
primary: 'bg-blue-600 text-white hover:bg-blue-700',
secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200',
ghost: 'hover:bg-gray-100 text-gray-700',
};
const sizes = { sm: 'px-3 py-1.5 text-sm', md: 'px-4 py-2', lg: 'px-6 py-3 text-lg' };
return (
<button
className={`rounded-lg font-medium transition-colors ${variants[variant]} ${sizes[size]} ${className}`}
{...props}
/>
);
}
Using the Shared Package in an App
// apps/web/package.json — add dependency
{
"dependencies": {
"@repo/ui": "workspace:*"
}
}
// apps/web/src/app/page.tsx
import { Button } from '@repo/ui/button';
export default function HomePage() {
return <Button variant="primary">Get Started</Button>;
}
Remote Caching for CI
# Connect to Vercel Remote Cache (free for personal use)
npx turbo login
npx turbo link
# CI workflow with cache
- run: pnpm turbo build --team=your-team
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: your-team
With remote caching, the second CI run of unchanged code completes in seconds rather than minutes — the cache is shared across all developers and CI machines.
Không spam, hủy đăng ký bất kỳ lúc nào.


