Monorepos let you manage multiple apps and shared packages in one repository. Turborepo makes this fast with build caching.
Step 1: Create the Monorepo
npx create-turbo@latest my-monorepo
cd my-monorepo
Or set up manually:
mkdir my-monorepo && cd my-monorepo
pnpm init
Step 2: Workspace Structure
my-monorepo/
├── apps/
│ ├── web/ # Main Next.js app
│ ├── docs/ # Documentation site
│ └── admin/ # Admin dashboard
├── packages/
│ ├── ui/ # Shared UI components
│ ├── config/ # Shared configs (ESLint, TypeScript)
│ ├── database/ # Database schema and client
│ └── utils/ # Shared utilities
├── turbo.json
├── package.json
└── pnpm-workspace.yaml
Step 3: Configure pnpm Workspaces
# pnpm-workspace.yaml
packages:
- "apps/*"
- "packages/*"
Step 4: Root package.json
{
"name": "my-monorepo",
"private": true,
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev",
"lint": "turbo run lint",
"test": "turbo run test",
"format": "prettier --write \"**/*.{ts,tsx,md}\""
},
"devDependencies": {
"prettier": "^3.4.0",
"turbo": "^2.3.0",
"typescript": "^5.7.0"
},
"packageManager": "pnpm@9.15.0"
}
Step 5: turbo.json Configuration
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "!.next/cache/**", "dist/**"]
},
"dev": {
"cache": false,
"persistent": true
},
"lint": {
"dependsOn": ["^build"]
},
"test": {
"dependsOn": ["^build"]
},
"db:generate": {
"cache": false
},
"db:migrate": {
"cache": false
}
}
}
Step 6: Shared UI Package
// packages/ui/package.json
{
"name": "@repo/ui",
"version": "0.0.0",
"private": true,
"exports": {
"./button": "./src/button.tsx",
"./card": "./src/card.tsx",
"./input": "./src/input.tsx",
"./badge": "./src/badge.tsx",
"./globals.css": "./src/globals.css"
},
"devDependencies": {
"@repo/config": "workspace:*",
"typescript": "^5.7.0"
},
"peerDependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0"
}
}
// packages/ui/src/button.tsx
import { forwardRef } from "react";
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: "primary" | "secondary" | "outline" | "ghost";
size?: "sm" | "md" | "lg";
}
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ variant = "primary", size = "md", className, children, ...props }, ref) => {
const variants = {
primary: "bg-blue-600 text-white hover:bg-blue-700",
secondary: "bg-gray-100 text-gray-900 hover:bg-gray-200",
outline: "border border-gray-300 hover:bg-gray-50",
ghost: "hover:bg-gray-100",
};
const sizes = {
sm: "px-3 py-1.5 text-sm",
md: "px-4 py-2 text-sm",
lg: "px-6 py-3 text-base",
};
return (
<button
ref={ref}
className={`inline-flex items-center justify-center rounded-lg font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50 ${variants[variant]} ${sizes[size]} ${className ?? ""}`}
{...props}
>
{children}
</button>
);
}
);
Button.displayName = "Button";
Step 7: Shared Database Package
// packages/database/package.json
{
"name": "@repo/database",
"version": "0.0.0",
"private": true,
"exports": {
".": "./src/index.ts",
"./schema": "./src/schema/index.ts"
},
"scripts": {
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:studio": "drizzle-kit studio"
},
"dependencies": {
"drizzle-orm": "^0.38.0",
"@neondatabase/serverless": "^0.10.0"
},
"devDependencies": {
"drizzle-kit": "^0.30.0"
}
}
// packages/database/src/index.ts
import { drizzle } from "drizzle-orm/neon-serverless";
import { neon } from "@neondatabase/serverless";
import * as schema from "./schema";
export function createDb(databaseUrl: string) {
const sql = neon(databaseUrl);
return drizzle({ client: sql, schema });
}
export type Database = ReturnType<typeof createDb>;
export * from "./schema";
Step 8: Shared Utils Package
// packages/utils/package.json
{
"name": "@repo/utils",
"version": "0.0.0",
"private": true,
"exports": {
".": "./src/index.ts"
}
}
// packages/utils/src/index.ts
export function formatCurrency(amount: number, currency: string = "USD"): string {
return new Intl.NumberFormat("en-US", { style: "currency", currency }).format(amount);
}
export function slugify(text: string): string {
return text
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/(^-|-$)/g, "");
}
export function truncate(text: string, maxLength: number): string {
if (text.length <= maxLength) return text;
return text.slice(0, maxLength).trimEnd() + "...";
}
export function cn(...classes: (string | undefined | null | false)[]): string {
return classes.filter(Boolean).join(" ");
}
Step 9: App Configuration
// apps/web/package.json
{
"name": "@repo/web",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
"lint": "next lint"
},
"dependencies": {
"@repo/ui": "workspace:*",
"@repo/database": "workspace:*",
"@repo/utils": "workspace:*",
"next": "^15.0.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
}
}
Step 10: Use Shared Packages
// apps/web/app/page.tsx
import { Button } from "@repo/ui/button";
import { Card } from "@repo/ui/card";
import { formatCurrency, truncate } from "@repo/utils";
export default function HomePage() {
return (
<main>
<h1>Welcome</h1>
<p>Price: {formatCurrency(29.99)}</p>
<p>{truncate("This is a long description...", 20)}</p>
<Button variant="primary">Get Started</Button>
</main>
);
}
Step 11: Shared TypeScript Config
// packages/config/tsconfig/base.json
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "bundler",
"module": "esnext",
"target": "es2022",
"lib": ["es2022", "dom", "dom.iterable"],
"jsx": "react-jsx",
"isolatedModules": true,
"incremental": true
},
"exclude": ["node_modules"]
}
Step 12: Development Workflow
# Start all apps in dev mode
pnpm dev
# Start specific app
pnpm --filter @repo/web dev
# Build all packages
pnpm build
# Add dependency to a specific package
pnpm --filter @repo/web add lodash
# Lint everything
pnpm lint
Summary
- Turborepo caches and parallelizes builds across packages
- Shared packages eliminate code duplication
workspace:*links packages without publishing- Each app can be deployed independently
- Single source of truth for configs and types
Need Monorepo Architecture?
We design and implement scalable monorepo architectures for growing engineering teams. Contact us to discuss your project.