· claude-code / monorepo / turborepo

Thiết lập Claude Code cho monorepo: Hướng dẫn đầy đủ 2026

Claude Code chạy tốt trong monorepo khi thiết lập đúng. Hướng dẫn CLAUDE.md hierarchy, per-package scoping, --add-dir và worktrees. Kiểm chứng v2.1.141.

Bởi Ethan

2.016 từ · 11 phút đọc

Claude Code hoạt động tốt trong monorepo — một khi bạn nắm được một hành vi không hiển nhiên: các file CLAUDE.md trong thư mục con không được tải khi khởi động. Chúng chỉ được tải khi cần, vào lần đầu tiên Claude đọc một file trong thư mục đó. Bỏ qua điều này, bạn sẽ ngồi cả buổi thắc mắc tại sao các quy ước per-package của mình bị phớt lờ.

Bài viết này hướng dẫn thiết lập monorepo pnpm + Turborepo với Claude Code v2.1.141. Mỗi bước đều là lệnh terminal bạn có thể chạy ngay hôm nay.

Dành cho ai

Dành cho developer đang dùng Claude Code và muốn chuyển project sang monorepo, hoặc bắt đầu monorepo mới và muốn Claude Code được thiết lập đúng ngay từ đầu. Nếu bạn chưa cài Claude Code, chạy npm install -g @anthropic-ai/claude-code và thử với một project đơn-package trước. Nếu bạn đang cân nhắc giữa Claude Code và Cursor, xem Claude Code vs Cursor 2026 để so sánh trực tiếp.

Tại sao monorepo làm phức tạp Claude Code

Khi chuyển từ single package sang monorepo, có bốn vấn đề nổi lên:

Áp lực context window. Claude đọc từ thư mục làm việc lên trên và tải tất cả file CLAUDE.md gặp được. Trong monorepo phức tạp, đó là root + per-app + per-package — trước khi bạn gõ một từ nào. Các file rule lớn cộng dồn nhanh.

Nhập nhằng đường dẫn với pnpm symlinks. node_modules/@acme/ui là symlink trỏ đến packages/ui/. Claude sẽ đi theo symlink đó. Nếu React được tải hai lần qua ranh giới symlink, bạn sẽ gặp hành vi bất ngờ — các module dùng chung state không nên dùng chung.

Phạm vi mặc định. Theo mặc định, Claude Code chỉ đọc và chỉnh sửa file trong thư mục làm việc hiện tại. Chạy từ packages/api/ có nghĩa Claude không thấy packages/shared/ trừ khi bạn mở rộng phạm vi rõ ràng.

Ma sát permission. Mỗi thư mục mới Claude cố truy cập trong chế độ mặc định sẽ kéo lên một prompt xác nhận. Điều đó ổn cho việc khám phá lúc đầu; nhưng khi làm việc cross-package liên tục thì mất đi sự tập trung hoàn toàn.

1. Tạo monorepo

pnpm dlx create-turbo@latest my-monorepo
cd my-monorepo

Lệnh này tạo ra Turborepo với pnpm workspaces, thư mục apps/packages/. Kiểm tra build trước khi đụng đến Claude Code:

pnpm turbo build

2. Viết root CLAUDE.md

Root CLAUDE.md được tải mỗi khi bạn chạy claude từ gốc repo. Giữ dưới 200 dòng — file dài hơn bắt đầu bị bỏ qua. Đặt vào đây: package manager, lệnh build, và các quy tắc áp dụng toàn repo. Không có gì khác.

cat > CLAUDE.md << 'EOF'
# Monorepo: @acme

## Package manager
Always use pnpm. Never npm or yarn.
- Install: `pnpm add <pkg> --filter @acme/<package>`
- Build all: `pnpm turbo build`
- Test all: `pnpm turbo test`
- Dev server: `pnpm turbo dev`

## Structure
- apps/       — deployable applications
- packages/   — shared internal libraries
- tooling/    — shared configs (ESLint, TypeScript base)

## Cross-cutting rules
- Internal packages use the `workspace:*` protocol
- Never import from another package's `src/` — import by package name
- No circular dependencies (`pnpm turbo lint` checks this)
- TypeScript strict mode everywhere
- Named exports only
- `@acme/types` is the single source of truth for shared interfaces
EOF

Nếu bạn muốn Claude tự tạo từ codebase:

CLAUDE_CODE_NEW_INIT=1 claude   # quy trình tương tác nhiều giai đoạn /init

Flag CLAUDE_CODE_NEW_INIT=1 kích hoạt quy trình tương tác nhiều giai đoạn: Claude khám phá codebase bằng subagent, hỏi bạn muốn thiết lập những gì (file CLAUDE.md, skills, hooks), bổ sung thông tin còn thiếu qua các câu hỏi tiếp theo, rồi đưa ra bản đề xuất để bạn xem xét trước khi ghi.

Những gì không nên đặt trong root CLAUDE.md: quy ước framework cụ thể (Next.js page router so với app router, Prisma patterns), URL dev hoặc credentials cá nhân (đặt trong CLAUDE.local.md, đã được gitignore), và các quy trình nhiều bước. Những thứ đó thì chuyển thành skills.

3. Viết file CLAUDE.md cho từng package

Mỗi package có file CLAUDE.md riêng cho các quy tắc framework cụ thể. Đây là ví dụ cho package API dùng Express:

cat > packages/api/CLAUDE.md << 'EOF'
# @acme/api

Framework: Express.js + TypeScript
Database: Prisma (schema at prisma/schema.prisma)
Validation: Zod

## Commands
Build: pnpm --filter @acme/api build
Test:  pnpm --filter @acme/api test
Dev:   pnpm --filter @acme/api dev

## Conventions
- Routes live in src/routes/
- All inputs validated with Zod
- Async error handler wraps every route handler
- Use @acme/types for shared interfaces — never redefine locally
EOF

Hành vi quan trọng cần nắm: file này KHÔNG được tải khi bạn khởi động Claude từ gốc repo. Nó chỉ được tải khi cần, vào lần đầu tiên Claude đọc file nào đó trong packages/api/. Chạy Claude từ bên trong packages/api/ là khác — khi đó cả root CLAUDE.md lẫn package CLAUDE.md đều được tải ngay khi khởi động.

Với team lớn khi file per-directory trở nên rối, hãy dùng path-scoped rules. Tạo .claude/rules/api.md tại gốc repo:

---
paths:
  - "packages/api/**"
---
# API Package Rules
- All API endpoints must include Zod input validation
- Use the standard error response format from @acme/types

Các quy tắc này chỉ được tải khi Claude làm việc với các đường dẫn file khớp. Kết quả tương tự, nhưng tập trung ở một chỗ.

4. Chọn thư mục làm việc

Cách bạn khởi động Claude Code quyết định những gì được tải và những gì bạn có thể truy cập:

Từ gốc repoTừ thư mục package
Tải khi khởi độngChỉ root CLAUDE.mdRoot + package CLAUDE.md
Package CLAUDE.mdTải khi cầnTải khi khởi động
Quyền truy cập mặc địnhToàn bộ repoChỉ thư mục package
Chỉnh sửa cross-packageDễ — không cần cấu hình thêmCần --add-dir
Rủi roCó thể chỉnh nhầm package khácKhông thấy package anh em nếu không có trợ giúp
Phù hợp choRefactor cross-package, thay đổi typesPhát triển tính năng trong một package

Khi làm việc trên một package nhưng cần truy cập code dùng chung, mở rộng phạm vi:

# Flag dùng một lần
claude --add-dir ../packages/shared --add-dir ../packages/types

Hoặc lưu vào packages/api/.claude/settings.json:

{
  "permissions": {
    "additionalDirectories": ["../packages/shared", "../packages/types"]
  }
}

Một giới hạn quan trọng: --add-dir cấp quyền truy cập file nhưng KHÔNG quét các thư mục được thêm để tìm sub-agents hay file CLAUDE.md bổ sung. Để tải CLAUDE.md từ các thư mục được thêm, đặt CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1.

5. Giảm ma sát permission

Phê duyệt trước các đường dẫn bạn biết Claude sẽ cần. Trong .claude/settings.json tại gốc repo:

{
  "permissions": {
    "allow": [
      "Read(./packages/**)",
      "Read(./apps/**)",
      "Edit(./packages/**)",
      "Edit(./apps/**)",
      "Bash(pnpm *)",
      "Bash(turbo *)",
      "Bash(git diff *)",
      "Bash(git status *)"
    ],
    "deny": [
      "Read(./.env*)",
      "Read(./secrets/**)"
    ]
  }
}

Commit file này. Mọi developer trong team sẽ dùng cùng cấu hình mặc định.

6. Thiết lập parallel sessions với worktrees

Để làm việc song song trên nhiều package, git worktrees cho phép bạn chạy các Claude Code session riêng biệt trên các nhánh khác nhau mà context không bị lẫn sang nhau:

git worktree add ../api-feature -b feat/api-auth
git worktree add ../web-feature -b feat/web-auth

# Chạy Claude Code riêng trong mỗi worktree
cd ../api-feature && claude
cd ../web-feature && claude

Với monorepo lớn, thêm cấu hình tối ưu worktree vào .claude/settings.json:

{
  "worktree": {
    "baseRef": "fresh",
    "symlinkDirectories": ["node_modules", ".turbo"],
    "sparsePaths": ["packages/api", "packages/shared", "tooling"]
  }
}

symlinkDirectories tạo symlink node_modules từ repo chính vào mỗi worktree — không cần chạy lại pnpm install. sparsePaths dùng git sparse-checkout cone mode, chỉ ghi các thư mục được liệt kê ra disk. Với monorepo có hơn 50 package, mỗi lần tạo worktree gần như tức thì.

Để chạy song song trong cùng một session, tạo sub-agents trong .claude/agents/:

mkdir -p .claude/agents

cat > .claude/agents/api-agent.md << 'EOF'
---
name: api-agent
description: Handles all changes to packages/api. Use when modifying API routes, Prisma schema, or Express middleware.
tools: [Read, Edit, Bash, Grep, Glob]
model: sonnet
---
You work exclusively in packages/api. Never modify files outside this directory.
Framework: Express.js + TypeScript. Always run `pnpm --filter @acme/api test` after changes.
EOF

Session chính của Claude phân công nhiệm vụ đến đúng sub-agent dựa trên phần mô tả. Mỗi sub-agent có context window riêng.

Để viết file skill tái sử dụng được cho Claude Code, xem Cách viết một Claude Code skill.

7. Những bẫy thường gặp

pnpm symlinks follow-through. node_modules/@acme/uipackages/ui/. Claude sẽ đi theo symlinks và có thể đề xuất chỉnh sửa bên trong packages/ui/ khi bạn đang làm việc ở apps/web/. Sau khi đổi tên package, các symlink cũ tạo ra lỗi ENOENT — sửa bằng pnpm install --force. Xem pnpm vs npm để hiểu thêm về mô hình symlink của pnpm so với npm truyền thống.

MCP servers lỗi trong pnpm repo nghiêm ngặt (lỗi đã biết; #38275 đóng là duplicate — kiểm tra parent để biết trạng thái hiện tại). Claude Code khởi động MCP servers tích hợp sẵn qua npx từ thư mục project. Nếu .npmrc của bạn yêu cầu pnpm là package manager, npx sẽ thất bại thầm lặng và các MCP tools biến mất. Được theo dõi tại anthropics/claude-code #38275. Giải pháp tạm thời: cấu hình khởi động MCP server rõ ràng bằng pnpm dlx.

Turbo cache đọc dữ liệu cũ. .turbo/ lưu cache output build. Nếu cache cũ, Claude đọc các file dist/ lỗi thời. Thêm .turbo/ vào .gitignore (Turborepo mặc định làm điều này — kiểm tra lại xem có không) và đặt "cache": false cho các task test trong turbo.json.

claudeMdExcludes để cách ly team. Trong monorepo với nhiều team, Claude tải tất cả file CLAUDE.md ở các thư mục cha. Dùng setting này để chặn những file không liên quan:

{
  "claudeMdExcludes": ["**/packages/legacy-team/**/.claude/rules/**"]
}

worktree.baseRef thay đổi từ v2.1.133. Trước phiên bản này, EnterWorktree tạo nhánh từ HEAD cục bộ. Từ v2.1.133 trở đi, nó tạo từ origin/<default> (gọi là chế độ fresh). Nếu bạn có commits chưa push mà cần trong worktree mới, đặt:

{ "worktree": { "baseRef": "head" } }

Cấu trúc file để bắt đầu nhanh

my-monorepo/
├── CLAUDE.md                          ← package manager, structure, cross-cutting rules
├── CLAUDE.local.md                    ← gitignored: URL cá nhân, preferences
├── .claude/
│   ├── settings.json                  ← committed: permissions, additionalDirectories
│   ├── settings.local.json            ← gitignored: overrides cá nhân
│   ├── agents/
│   │   ├── api-agent.md               ← sub-agent cho packages/api
│   │   └── web-agent.md               ← sub-agent cho apps/web
│   └── rules/
│       ├── api.md                     ← paths: ["packages/api/**"]
│       └── web.md                     ← paths: ["apps/web/**"]
├── pnpm-workspace.yaml
├── turbo.json
├── apps/
│   └── web/
│       └── CLAUDE.md                  ← quy ước Next.js, lệnh test
└── packages/
    ├── types/
    │   └── CLAUDE.md                  ← "thay đổi breaking ảnh hưởng đến tất cả dependents"
    ├── api/
    │   └── CLAUDE.md                  ← quy ước Express/Prisma
    └── shared/
        └── CLAUDE.md

Bổ sung vào .gitignore:

CLAUDE.local.md
.claude/settings.local.json
.turbo/
*.local

Để tự động tạo file CLAUDE.md per-package từ workspace definition:

pip install claude-init-monorepo
claude-init-monorepo   # đọc pnpm-workspace.yaml, chạy /init cho từng package

Tham khảo