Managing frontend and backend in separate repositories sounds clean — until you change an API response shape and spend two hours syncing type definitions across three repos.
Turborepo consolidates everything into a single repository with intelligent build caching. Claude Code reads your CLAUDE.md and generates the entire monorepo design from scratch. Here's how it works.
What You Tell Claude Code (CLAUDE.md)
## Monorepo Structure
- apps/ — deployable applications
- api/ — Express backend
- web/ — Next.js frontend
- admin/ — React admin panel
- packages/ — shared internal libraries
- types/ — shared TypeScript types (API contracts)
- ui/ — shared UI components
- config/ — shared ESLint, TypeScript configs
## Turbo Pipeline Rules
- ^build means upstream packages must build first before this app builds
- outputs specify what directories Turbo should cache
- CI: use remote cache + --filter to build only affected packages
- dev tasks are persistent (long-running, never complete)
## Dependency Protocol
- packages/types contains all shared API types (request/response interfaces)
- Apps depend on packages/types using workspace:* protocol
- Never duplicate type definitions across apps
This is your contract with Claude Code. It reads this before generating any structure, configuration, or code.
turbo.json: The Pipeline Definition
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**", "!.next/cache/**"]
},
"test": {
"dependsOn": ["^build"],
"outputs": [],
"cache": false
},
"lint": {
"outputs": []
},
"dev": {
"cache": false,
"persistent": true
}
}
}
The key insight: "dependsOn": ["^build"] means packages/types builds before apps/api or apps/web. Turbo resolves the dependency graph automatically.
cache: false for test ensures tests always run fresh. persistent: true for dev tells Turbo these are long-running processes that don't exit.
pnpm-workspace.yaml
packages:
- 'apps/*'
- 'packages/*'
Two lines. That's the entire workspace configuration. pnpm discovers every package under apps/ and packages/ automatically.
Shared Types Package
// packages/types/src/user.ts
export interface User {
id: string;
email: string;
displayName: string;
createdAt: string;
role: 'admin' | 'member' | 'viewer';
}
export interface UserListResponse {
users: User[];
total: number;
page: number;
pageSize: number;
}
// packages/types/package.json
{
"name": "@myapp/types",
"version": "0.0.1",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"dev": "tsc --watch"
}
}
The @myapp/types package compiles TypeScript and exports both JavaScript and type definitions. Any app that installs this package gets full type safety.
Consuming Types in Apps
// apps/api/package.json (relevant section)
{
"dependencies": {
"@myapp/types": "workspace:*"
}
}
workspace:* tells pnpm to link directly to the local package instead of downloading from npm. When you change a type in packages/types, every app sees the change immediately after rebuilding.
// apps/api/src/routes/users.ts
import type { UserListResponse } from '@myapp/types';
export async function getUserList(req: Request, res: Response) {
const users = await db.user.findMany({ take: 20 });
const response: UserListResponse = {
users,
total: await db.user.count(),
page: 1,
pageSize: 20,
};
res.json(response);
}
// apps/web/src/hooks/useUsers.ts
import type { UserListResponse } from '@myapp/types';
export function useUsers() {
return useSWR<UserListResponse>('/api/users');
}
Both the API and the web app use the same UserListResponse type. If you add a field to User, TypeScript will catch every place that needs to be updated — across both apps, in one terminal window.
GitHub Actions: CI with Remote Cache
name: CI
on:
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm turbo build
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
- name: Test (affected only)
run: pnpm turbo test --filter='[HEAD^1]'
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
TURBO_TOKEN enables Turbo's remote cache. When the same build runs again with identical inputs, Turbo downloads the cached dist/ instead of rebuilding. On large monorepos, this cuts CI time by 60-80%.
--filter='[HEAD^1]' tells Turbo to only run tests for packages that changed since the previous commit. If you only edited apps/web, the API tests don't run.
What Claude Code Actually Generates From This
When you hand Claude Code the CLAUDE.md above and say "scaffold this monorepo," it produces:
- Complete directory structure with all six packages
-
turbo.jsonwith correctdependsOnchains pnpm-workspace.yaml-
packages/types/with TypeScript config, build scripts, and initial type definitions -
packages/config/with sharedtsconfig.base.jsonand ESLint config - Each app's
package.jsonwithworkspace:*references - A root
package.jsonwithturboscripts
The CLAUDE.md makes it consistent. Without it, Claude Code might put types directly in apps/api, or forget the ^build dependency, or use npm instead of pnpm.
Summary
| Piece | Purpose |
|---|---|
| CLAUDE.md | Tells Claude Code the exact structure and rules |
packages/types |
Single source of truth for all API contracts |
workspace:* |
Links packages locally without npm publishing |
turbo.json pipeline |
Correct build order + aggressive caching |
--filter='[HEAD^1]' |
CI runs only affected packages |
The result: one git clone, one pnpm install, one pnpm dev to run everything. Type changes propagate instantly. CI is fast because of remote cache and filtered runs.
If you want to go deeper on Claude Code workflow design — including how to structure CLAUDE.md for different project types — my Code Review Pack covers multi-file review prompts and project configuration patterns.
Code Review Pack (¥980) — available at prompt-works.jp under /code-review
What's your current monorepo setup? Turborepo, Nx, or still separated repos?
Top comments (0)