Monorepo 工程化实战:Turborepo 与 Nx 深度选型及配置指南

By | 2026年10月16日

一、为什么 Monorepo 又火了?

过去几年,前端工程化经历了从「单仓库单应用」到「多仓库多应用」的拆分,如今又回归到「单仓库多应用」的 Monorepo 模式。这不是简单的循环,而是工具链成熟后的必然选择:

  • 代码复用:组件库、工具函数、类型定义可以跨项目直接引用,无需发包。
  • 原子提交:一次修改同时更新多个包,保证依赖一致性。
  • 统一工具链:一套 ESLint、TypeScript、Vite 配置覆盖所有项目。
  • 高效协作:新人克隆一个仓库就能启动全部开发环境。

但 Monorepo 也带来了新问题:构建慢、任务编排复杂、依赖图难以管理。这正是 Turborepo 和 Nx 要解决的核心痛点。

二、Turborepo vs Nx:核心差异与选型

2.1 设计哲学

| 维度 | Turborepo | Nx | |——|———–|—–| | 定位 | 高性能任务运行器 | 全能型工程平台 | | 学习曲线 | 平缓,配置极简 | 陡峭,概念较多 | | 插件生态 | 无插件,靠 npm scripts | 丰富的官方/社区插件 | | 缓存粒度 | 任务级 | 任务级 + 文件级 | | 远程缓存 | 支持(Vercel 等) | 支持(Nx Cloud) | | 代码生成 | 无 | 强大的 generator | | 适用场景 | 中小团队,快速上手 | 大型企业,复杂架构 |

我的选型建议:

  • 如果你的团队已经有一套成熟的 npm scripts,只想加速构建和缓存,Turborepo 是首选。
  • 如果你需要依赖图可视化、代码生成器、模块边界约束、插件化架构,Nx 更合适。
  • 如果项目使用 Vite + pnpm,Turborepo 几乎零配置;如果项目是 Angular 或需要微前端支持,Nx 开箱即用。

2.2 性能对比实测

在一个包含 3 个应用(Next.js + Vite + Node)和 5 个共享包(UI、utils、config)的测试仓库中,冷启动构建耗时:

  • Turborepo:42s(首次),6s(缓存命中)
  • Nx:48s(首次),5s(缓存命中)

差异不大,但 Turborepo 的配置量只有 Nx 的 1/3。

三、从零搭建 Turborepo 项目(推荐入门)

3.1 初始化


npx create-turbo@latest my-monorepo
cd my-monorepo

选择 pnpm 作为包管理器。生成的目录结构:


my-monorepo/
├── apps/
│   ├── web/          # Next.js 应用
│   └── docs/         # 文档站
├── packages/
│   ├── ui/           # 共享组件库
│   ├── eslint-config/
│   └── typescript-config/
├── package.json
├── pnpm-workspace.yaml
└── turbo.json

3.2 核心配置 turbo.json


{
  "$schema": "https://turbo.build/schema.json",
  "globalDependencies": ["**/.env.*local"],
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": [".next/**", "!.next/cache/**", "dist/**"]
    },
    "lint": {
      "dependsOn": ["^lint"]
    },
    "test": {
      "dependsOn": ["build"],
      "outputs": ["coverage/**"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

关键点:

  • dependsOn: ["^build"] 表示当前包的 build 依赖所有上游包的 build。
  • outputs 声明缓存产物,未声明的文件不会命中缓存。
  • persistent: true 用于 dev 这类长期运行的任务。

3.3 包内脚本配置

以 packages/ui/package.json 为例:


{
  "name": "@repo/ui",
  "version": "0.0.0",
  "exports": {
    "./button": "./src/button.tsx",
    "./card": "./src/card.tsx"
  },
  "scripts": {
    "lint": "eslint . --max-warnings 0",
    "build": "tsc --noEmit",
    "test": "vitest run"
  }
}

注意:Turborepo 不关心包管理器如何链接,它只负责调度任务。因此必须确保 pnpm-workspace.yaml 正确声明:


packages:
  - "apps/*"
  - "packages/*"

3.4 运行任务


pnpm turbo build          # 构建所有包
pnpm turbo build --filter=web  # 只构建 web 及其依赖
pnpm turbo dev            # 并行启动所有 dev 任务

💡 踩坑提示:如果 --filter 不生效,检查包名是否与 package.json 中的 name 一致。Turborepo 使用包名而非目录名过滤。

四、Nx 进阶配置(适合复杂场景)

4.1 初始化


npx create-nx-workspace@latest my-org --preset=ts

选择 Integrated Monorepo 模式,包管理器选 pnpm。

4.2 添加应用与库


nx g @nx/react:app apps/admin
nx g @nx/js:lib packages/utils

Nx 会自动生成 project.json 并维护依赖图。

4.3 模块边界约束

在 nx.json 中配置:


{
  "targetDefaults": {
    "build": {
      "dependsOn": ["^build"],
      "cache": true,
      "outputs": ["{projectRoot}/dist"]
    }
  },
  "plugins": [
    {
      "plugin": "@nx/eslint/plugin",
      "options": {
        "targetName": "lint"
      }
    }
  ],
  "nxCloudAccessToken": "your-token"
}

再通过 .eslintrc.json 中的 @nx/enforce-module-boundaries 规则限制跨层引用:


{
  "rules": {
    "@nx/enforce-module-boundaries": [
      "error",
      {
        "depConstraints": [
          {
            "sourceTag": "scope:app",
            "onlyDependOnLibsWithTags": ["scope:shared", "scope:feature"]
          }
        ]
      }
    ]
  }
}

4.4 远程缓存


nx connect-to-cloud

Nx Cloud 提供免费额度,团队成员共享缓存,CI 提速显著。

五、最佳实践与性能优化

5.1 精准声明 inputs

Turborepo 默认将包内所有文件作为输入,但你可以细化:


{
  "pipeline": {
    "build": {
      "inputs": ["src/**", "package.json", "tsconfig.json"],
      "outputs": ["dist/**"]
    }
  }
}

避免因无关文件(如 README)变更导致缓存失效。

5.2 利用 pnpm catalog 统一依赖版本

在 pnpm-workspace.yaml 中:


catalog:
  react: ^18.3.1
  typescript: ^5.5.4

包内引用:


{
  "dependencies": {
    "react": "catalog:"
  }
}

彻底解决多包版本不一致问题。

5.3 CI 中的缓存策略

GitHub Actions 示例:


- uses: actions/cache@v4
  with:
    path: .turbo
    key: ${{ runner.os }}-turbo-${{ github.sha }}
    restore-keys: |
      ${{ runner.os }}-turbo-

- run: pnpm turbo build --cache-dir=.turbo

5.4 常见坑

  • 循环依赖:Turborepo 会直接报错,Nx 会给出警告。建议用 madge 提前检测。
  • dev 任务阻塞:persistent: true 的任务不能依赖其他 persistent 任务,否则死锁。
  • Docker 构建:Monorepo 镜像体积大,建议用 pnpm deploy --filter 生成独立产物。

六、总结

Monorepo 不是银弹,但 Turborepo 和 Nx 让它的门槛大幅降低。

  • Turborepo:轻量、快速、配置简单,适合大多数中小团队。
  • Nx:功能全面、生态强大,适合需要强约束和代码生成的大型项目。

无论选择哪个,核心原则不变:明确任务依赖、精准缓存输入输出、统一依赖版本。建议先从 Turborepo 开始,当遇到插件化或架构约束需求时再迁移到 Nx,迁移成本并不高。

下一步可以探索:远程缓存自建(如 turborepo-remote-cache)、变更影响分析(nx affected)、以及 Monorepo 的发布流程(changesets)。