你有一個 Next.js 前端、一個 admin dashboard、幾個共用的 utility package。一開始放在不同 repo 裡,每次改一個 shared type 就要跨 repo 發 PR、等 publish、更新版本號。三個 repo 的 ESLint 和 TypeScript config 慢慢飄開,最後沒人記得哪個 repo 用了哪個版本。
Monorepo 解決的就是這個問題。
Monorepo 是什麼
Monorepo 是把多個 project 放在同一個 Git repository 裡,共用一份 dependency tree、一套 config、一條 CI pipeline。它不是把所有 code 塞進同一個資料夾——而是在一個 repo 裡維護清楚的專案邊界。
什麼時候值得用:
- 你有 2 個以上的 app 共用邏輯(UI 元件、型別、工具函式)
- 你花太多時間在跨 repo 同步 config 和版本
- 你希望一個 PR 能同時改 app 和它依賴的 package
什麼時候不需要:
- 你只有一個 app,沒有共用模組
- 團隊成員之間的 codebase 完全獨立
- 你的 CI/CD 跑不了 monorepo(不過現在幾乎都支援了)
獨立開發者一個人維護 2-3 個 app 加幾個 shared package,monorepo 反而比多 repo 更省事。
Turborepo vs Nx
Monorepo 工具有兩大選擇:Turborepo 和 Nx。
| Turborepo | Nx | |
|---|---|---|
| 定位 | Task runner + caching | 完整 monorepo 框架 |
| 學習成本 | 低,設定一個 turbo.json | 中高,有自己的 plugin 系統 |
| 設定量 | 極少,幾行 JSON | 較多,需要理解 project graph |
| Generator | 無內建 | 內建 code generator |
| Plugin 生態 | 無(刻意精簡) | 豐富(React、Angular、Node 等) |
| Remote Cache | Vercel 原生支援 | Nx Cloud |
| 適合 | 中小型專案、想保持簡單 | 大型專案、需要嚴格的架構約束 |
Turborepo 的哲學是「做好一件事」——它只管 task orchestration 和 caching,其他都交給你現有的工具鏈。如果你已經有 pnpm workspaces 跑得好好的,加上 Turborepo 只需要一個設定檔。
Nx 功能更全,但學習曲線也更陡。對獨立開發者或小團隊來說,Turborepo 的簡單是優勢。
從零建立:pnpm workspaces + Turborepo
Step 1:初始化專案
mkdir my-monorepo && cd my-monorepo
pnpm init
建立 pnpm-workspace.yaml:
# pnpm-workspace.yaml
packages:
- 'apps/*'
- 'packages/*'
這告訴 pnpm:apps/ 和 packages/ 下的每個資料夾都是一個獨立的 workspace package。
Step 2:安裝 Turborepo
pnpm add -D turbo -w
-w 是安裝到 workspace root。
Step 3:建立目錄結構
my-monorepo/
├── apps/
│ ├── web/ # 主要前端 (Next.js)
│ │ ├── package.json
│ │ └── ...
│ └── admin/ # Admin dashboard (Next.js)
│ ├── package.json
│ └── ...
├── packages/
│ ├── ui/ # 共用 UI 元件
│ │ ├── package.json
│ │ └── src/
│ ├── utils/ # 共用工具函式
│ │ ├── package.json
│ │ └── src/
│ └── config/ # 共用 ESLint、TypeScript config
│ ├── eslint/
│ └── typescript/
├── turbo.json
├── pnpm-workspace.yaml
└── package.json
每個 apps/* 和 packages/* 下的資料夾都有自己的 package.json,定義自己的 name、dependencies 和 scripts。
Step 4:設定 package 之間的依賴
在 apps/web/package.json 裡引用共用 package:
{
"name": "@apps/web",
"dependencies": {
"@packages/ui": "workspace:*",
"@packages/utils": "workspace:*"
}
}
workspace:* 是 pnpm 的語法,表示「用 workspace 裡的版本」。不需要 publish,不需要版本號,改了立刻生效。
turbo.json:定義 task pipeline
這是 Turborepo 的核心設定:
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "dist/**"]
},
"dev": {
"cache": false,
"persistent": true
},
"lint": {
"dependsOn": ["^build"]
},
"test": {
"dependsOn": ["build"]
}
}
}
幾個關鍵概念:
dependsOn: ["^build"]—^表示先跑所有上游依賴的 build。當你跑turbo run build,Turborepo 會先 buildpackages/ui、packages/utils,再 buildapps/weboutputs— 告訴 Turborepo 哪些檔案是 build 產物,用於 cachecache: false—dev不需要 cache,因為它是 long-running processpersistent: true— 標記dev是持續運行的 task,不會阻塞其他 task
跑 pnpm turbo run build,Turborepo 會自動解析依賴圖、平行執行、跳過沒有變更的 package。
常用 packages 拆法
packages/ui
共用 React 元件。Button、Card、Modal、Input 這些在多個 app 都會用到的元件放這裡:
{
"name": "@packages/ui",
"exports": {
"./*": "./src/*.tsx"
}
}
在 app 裡直接 import:
import { Button } from '@packages/ui/button'
packages/utils
純函式工具:日期格式化、字串處理、type guards。不要放 React 相關的東西,保持 runtime agnostic。
packages/config
共用設定檔。ESLint config、TypeScript base config、Tailwind preset。每個 app 的 config 都 extend 這裡的 base:
// apps/web/tsconfig.json
{
"extends": "@packages/config/typescript/nextjs.json",
"compilerOptions": {
"outDir": "dist"
}
}
一個地方改,所有 app 同步更新。不會再出現 A 專案用 strict mode、B 專案沒用的情況。
Remote Caching
Turborepo 最實用的功能之一。本地 cache 只對你自己有效,remote cache 讓整個 CI 和團隊共享 build 結果。
# 登入 Vercel(Turborepo 的 remote cache 由 Vercel 提供)
pnpm turbo login
# 連結到你的 Vercel team
pnpm turbo link
設定完之後,CI 第一次 build 的結果會上傳到 remote cache。下次任何人(或 CI runner)跑同樣的 build,如果 input 沒變,直接從 cache 拿結果,build 時間從幾分鐘變成幾秒。
對獨立開發者來說,最大的好處是 CI 加速。你的 GitHub Actions 不用每次都重新 build 所有 package。
實際效益
用 Turborepo 管理一個有 3 個 app + 5 個 package 的 monorepo,日常開發的體感差異:
- 改一個 shared type — 以前:改 package → publish → 更新三個 app 的版本 → 三個 PR。現在:改一次,三個 app 自動拿到
- 跑 build — 以前:三個 repo 各跑一次。現在:
turbo run build,沒改的 package 直接用 cache - 加新 app — 以前:從零設定 ESLint、TypeScript、CI。現在:建個資料夾,extend 共用 config,done
- CI 時間 — 啟用 remote cache 後,只有改到的 package 會重新 build
Monorepo 不是銀彈,但對「一個人維護多個相關專案」的場景,它是目前最實用的架構選擇。
想看實際的 monorepo 架構怎麼搭配 Supabase、Polar 和 Next.js 建構完整的學習平台?到 Docs 查看我們的架構設計和實作指南。