起因
公司项目演化:
- 1 个 web app → 加 mobile app(React Native)
- 加 admin dashboard
- 加 marketing site
- 加 design system 组件库
- 加 shared TS types / API client
多 repo 痛点:
- shared code 要 publish package
- 跨 repo PR 难 coordinate
- 升级 dep 各 repo 跟不齐
monorepo 解决但 build / test / cache 是新挑战。
Turborepo / Nx 解决"哪些 task 要跑 + 缓存什么"。
Turborepo
Vercel 收购,2022+。
monorepo/
├── apps/
│ ├── web/ # Next.js
│ ├── mobile/ # React Native
│ └── admin/
├── packages/
│ ├── ui/ # shared component
│ ├── api-client/
│ └── tsconfig/
├── package.json
├── turbo.json
└── pnpm-workspace.yaml
pnpm-workspace.yaml:
packages:
- 'apps/*'
- 'packages/*'
turbo.json:
{
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "dist/**"]
},
"test": {
"dependsOn": ["build"]
},
"lint": {},
"dev": {
"cache": false,
"persistent": true
}
}
}
^build = 先跑依赖 package 的 build。
用法
pnpm turbo build # build 所有 package(按 dep order)
pnpm turbo test # 同上
pnpm turbo dev # 跑所有 dev server
pnpm turbo build --filter=web # 只 build web app + 它依赖
第一次跑 build:跑全部 task。
第二次跑 build(无改动):全部 cache hit,零执行。
cache 怎么工作
Turbo 把每 task 的:
- input file content hash
- env var
- dependency hash
→ cache key。命中 cache:直接复用 output(dist/)。
✔ ui:build cached (3.2s saved)
✔ api-client:build cached
○ web:build finished (cached) 4.5s
改 1 个 package 的 source → 它跟它的 dependent 重 build;其它仍 cache hit。
远程 cache
Turbo cache 默认本地。多人开发 / CI 同一计算重复跑。
remote cache:
npx turbo login
npx turbo link
把 cache 推 Vercel cloud / 自托管 → 同事 pull 后 cache 命中(同 commit
他不必再跑 build)。
我们 6 人团队 CI 时间从 12 分钟 → 2 分钟(90% cache hit)。
Nx
Nrwl 出,2017+。比 turbo 更老更全。
npx create-nx-workspace@latest myorg
Nx 提供:
- task scheduling + cache(跟 Turbo 类似)
- generator(一键 scaffold app / lib)
- 依赖图可视化
- 多语言(不只 JS,也支持 .NET / Go via plugin)
- consistent project structure 强制
nx.json:
{
"targetDefaults": {
"build": {
"cache": true,
"inputs": ["default", "^production"]
}
},
"tasksRunnerOptions": {
"default": {
"runner": "nx/tasks-runners/default",
"options": { "cacheableOperations": ["build", "test", "lint"] }
}
}
}
nx build web
nx affected -t test # 只跑受影响 package
nx graph # 浏览器看 dependency 图
nx affected 是杀器:基于 git diff 判定哪 package 改了,只跑相关。
Turbo vs Nx
| Turbo | Nx | |
|---|---|---|
| 设计 | 轻量 task runner | 全套 monorepo solution |
| 学习曲线 | 低 | 中高 |
| generator | 无 | 强(scaffold app / lib) |
| 依赖图 UI | 无 | 强 |
| 多语言 | JS only | JS + 插件 |
| 远程 cache | Vercel / 自托管 | Nx Cloud / 自托管 |
| 强约束 | 少 | 多("Nx way") |
Turbo:你已有 monorepo + 想加 cache → 5 分钟接入。
Nx:从 0 开始 + 想 batteries-included → Nx。
我个人 Turbo 多(不喜欢被框架强约束)。
内部 package 引用
// apps/web/package.json
{
"dependencies": {
"@myorg/ui": "workspace:*",
"@myorg/api-client": "workspace:*"
}
}
workspace:* 让 pnpm 链接到本地 package(不发 npm)。
// apps/web/app/page.tsx
import { Button } from '@myorg/ui';
import { fetchPosts } from '@myorg/api-client';
跟普通 npm package 一样 import。
shared tsconfig
// packages/tsconfig/base.json
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"strict": true,
"moduleResolution": "Bundler"
}
}
// apps/web/tsconfig.json
{
"extends": "@myorg/tsconfig/base",
"compilerOptions": { ... }
}
config 一处改 全 repo 受用。
CI 优化
# GitHub Actions
- uses: pnpm/action-setup@v2
- run: pnpm install
- run: pnpm turbo build test lint --concurrency=4
Turbo 跟 GH cache action 配合 → cache 远程拉。
PR 时间从 15 分钟 → 3 分钟(动几个 package 重 build,其余 cache)。
真实 case
我们 monorepo 演化:
最初: 单 Next.js 项目
↓
加 admin dashboard (Next.js) → 共享 component 想抽
↓
建 monorepo + pnpm workspace + Turbo
↓
packages/ui (shared component)
packages/api-types (类型 + zod schema)
packages/tsconfig
apps/web, apps/admin, apps/marketing
↓
后来加 packages/api-client (axios + types)
↓
team grow 到 8 人 → 加 remote cache
效果:
- 改 ui component → web + admin 一起测
- 共享 type 跨 frontend / backend(Node 后端也 import api-types)
- CI 快 5x
- 新成员 onboarding:clone 1 repo 全 setup
不要过早 monorepo
单 app + < 3 人 → 不必。
N app + shared code → monorepo + Turbo。
与 yarn workspaces / npm workspaces
pnpm 是 monorepo 最强:
- 严格 dependency 隔离
- 链接速度快
- workspaces 原生
npm workspaces / yarn workspaces 也行但 pnpm 主流。
踩过的坑
-
依赖循环:
@myorg/ui依赖@myorg/utils→ utils 依赖 ui →
build 报错。nx graph/turbo看依赖图理清。 -
build output 没指定:turbo 不知道 cache 啥 → cache miss 一直。
"outputs": [".next/**", "dist/**"]必填。 -
env 变量影响 cache:env 改了应该 invalidate cache。
"env": ["NODE_ENV"]配置。 -
monorepo 大 后 IDE 慢:100+ package 后 TS server 慢。
project references + 限 indexing scope。 -
publish 仍麻烦:内部 package 不 publish OK。但要 publish
shared design system → changesets 工具帮 version + changelog。
登录后参与评论。