Agent skill
monorepo-structure
Set up a Turborepo + pnpm monorepo for sharing code between frontend, backend, and workers. One repo, multiple packages, shared types, parallel builds.
Stars
163
Forks
31
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/monorepo-structure
Metadata
Additional technical details for this skill
- time
- 2h
- source
- drift-masterguide
- category
- foundations
SKILL.md
Monorepo Structure
One repo, multiple packages, shared types, parallel builds.
When to Use This Skill
- Sharing code between frontend and backend
- Multiple apps need common types/utilities
- Want atomic commits across packages
- Tired of version hell with separate repos
- Need parallel builds with caching
Core Concepts
- Workspaces - pnpm manages multiple packages in one repo
- Turborepo - Orchestrates builds with caching and parallelization
- Shared types - Single source of truth for TypeScript types
- Build order - Dependencies build before dependents
Project Structure
project-root/
├── apps/
│ ├── web/ # Next.js frontend
│ │ ├── app/
│ │ ├── components/
│ │ └── package.json
│ ├── api/ # Backend API
│ │ ├── src/
│ │ └── package.json
│ └── worker/ # Background worker
│ ├── src/
│ └── package.json
│
├── packages/
│ ├── types/ # Shared TypeScript types
│ │ ├── src/
│ │ │ ├── index.ts
│ │ │ ├── user.ts
│ │ │ └── schemas.ts
│ │ └── package.json
│ ├── utils/ # Shared utilities
│ │ └── package.json
│ └── config/ # Shared configs (eslint, tsconfig)
│ └── package.json
│
├── package.json # Root package.json
├── pnpm-workspace.yaml
├── turbo.json
└── tsconfig.base.json
TypeScript Implementation
pnpm-workspace.yaml
yaml
packages:
- "apps/*"
- "packages/*"
Root package.json
json
{
"name": "my-saas",
"private": true,
"scripts": {
"dev": "turbo dev",
"build": "turbo build",
"test": "turbo test",
"lint": "turbo lint",
"typecheck": "turbo typecheck",
"clean": "turbo clean && rm -rf node_modules"
},
"devDependencies": {
"turbo": "^2.0.0",
"typescript": "^5.4.0"
},
"packageManager": "pnpm@9.0.0"
}
turbo.json
json
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": ["**/.env.*local"],
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**", "!.next/cache/**"]
},
"dev": {
"cache": false,
"persistent": true
},
"test": {
"dependsOn": ["^build"]
},
"typecheck": {
"dependsOn": ["^build"]
},
"lint": {
"dependsOn": ["^build"]
},
"clean": {
"cache": false
}
}
}
tsconfig.base.json
json
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022"],
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true
}
}
Shared Types Package
json
// packages/types/package.json
{
"name": "@myapp/types",
"version": "0.0.1",
"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",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"typescript": "^5.4.0"
},
"dependencies": {
"zod": "^3.23.0"
}
}
json
// packages/types/tsconfig.json
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"]
}
typescript
// packages/types/src/index.ts
export * from './user';
export * from './schemas';
typescript
// packages/types/src/user.ts
export interface User {
id: string;
email: string;
name: string;
role: 'admin' | 'user' | 'guest';
createdAt: Date;
}
export interface CreateUserInput {
email: string;
name: string;
role?: 'admin' | 'user' | 'guest';
}
typescript
// packages/types/src/schemas.ts
import { z } from 'zod';
export const UserSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
name: z.string().min(1),
role: z.enum(['admin', 'user', 'guest']),
createdAt: z.coerce.date(),
});
export const CreateUserSchema = z.object({
email: z.string().email(),
name: z.string().min(1),
role: z.enum(['admin', 'user', 'guest']).default('user'),
});
App Package Using Shared Types
json
// apps/web/package.json
{
"name": "@myapp/web",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
},
"dependencies": {
"@myapp/types": "workspace:*",
"next": "^14.0.0",
"react": "^18.0.0"
}
}
typescript
// apps/web/app/api/users/route.ts
import type { User, CreateUserInput } from '@myapp/types';
import { CreateUserSchema } from '@myapp/types';
export async function POST(request: Request) {
const body = await request.json();
// Validate with shared schema
const input = CreateUserSchema.parse(body);
// Create user...
const user: User = await createUser(input);
return Response.json(user);
}
Shared Utils Package
json
// packages/utils/package.json
{
"name": "@myapp/utils",
"version": "0.0.1",
"private": true,
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"scripts": {
"build": "tsc",
"dev": "tsc --watch"
},
"devDependencies": {
"typescript": "^5.4.0"
}
}
typescript
// packages/utils/src/index.ts
export function formatDate(date: Date): string {
return date.toISOString().split('T')[0];
}
export function slugify(text: string): string {
return text
.toLowerCase()
.replace(/[^\w\s-]/g, '')
.replace(/\s+/g, '-');
}
export function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
Common Commands
bash
# Install all dependencies
pnpm install
# Run all dev servers in parallel
pnpm dev
# Build everything (respects dependency order)
pnpm build
# Run tests across all packages
pnpm test
# Add dependency to specific package
pnpm add zod --filter @myapp/types
# Add dev dependency to root
pnpm add -D prettier -w
# Run command in specific package
pnpm --filter @myapp/web dev
# Run command in all packages matching pattern
pnpm --filter "@myapp/*" build
Dependency Flow
packages/types (source of truth)
↓
packages/utils (may import types)
↓
apps/web, apps/api, apps/worker (import both)
Turborepo handles build order via dependsOn: ["^build"] - packages always build before apps that depend on them.
.gitignore
gitignore
# Dependencies
node_modules/
# Build outputs
dist/
.next/
.turbo/
# Environment
.env
.env.local
.env.*.local
# IDE
.idea/
.vscode/
# OS
.DS_Store
Best Practices
- Use
workspace:*- Always for internal dependencies - Types flow down - Shared types package is the source of truth
- One tsconfig.base - Extend from root, override only what's needed
- Atomic commits - Change types and consumers in same commit
- Cache builds - Turborepo caches unchanged packages
Common Mistakes
- Using
^1.0.0instead ofworkspace:*for internal deps - Building packages individually instead of
turbo build - Circular dependencies between packages
- Not including
dist/in.gitignore - Forgetting
dependsOn: ["^build"]in turbo.json
Related Skills
Didn't find tool you were looking for?