monorepo-management について
JavaScript と TypeScript のモノレポを設計・運用するチーム向けのガイドです。ワークスペース、依存グラフ、タスクキャッシュ、CI、バージョン管理を扱います。
主な機能
- 01モノレポツールの選定
- 02ワークスペースのパッケージ構成
- 03依存グラフとタスクパイプライン
- 04リモートキャッシュの設定
- 05変更対象パッケージのCI選別
- 06Changesetsによるバージョン管理
活用シーン
- 01新しいモノレポのツール選び
- 02ワークスペースとビルド順の設定
- 03変更パッケージだけを対象とするCI
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ curiositech/ some_claude_skills --skill monorepo-managementインストール後、Claude Code で/monorepo-managementを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
.claude-plugin/plugin.jsonreferences/turborepo-patterns.mdworkspace-architecture.md詳細
モノレポ管理
JavaScript および TypeScript プロジェクト向けのモノレポツールとワークスペースアーキテクチャの専門知識を提供します。ツールの選定、タスクパイプラインの設定、依存関係グラフの管理、CI の最適化、バージョニング戦略を扱います。
使用する場面
次の場合に使用します:
- 新規リポジトリまたは移行するリポジトリで、Turborepo、Nx、Lerna、Rush のどれを選ぶか検討する
turbo.jsonのタスクパイプライン、リモートキャッシュ、Docker のプルーニングを設定する- ワークスペースパッケージを構成する:アプリ、パッケージ、共有設定、内部ライブラリ
- 適切なホイスティングルールを使用して pnpm または npm のワークスペースを設定する
- ワークスペースパッケージ間の循環依存を解消する
- 影響を受けるパッケージを検出する、パスベースの CI を設定する
- Changesets でパッケージのバージョニングと変更履歴を管理する
対象外:
- Git サブモジュールまたはポリレポの調整 → トレードオフを別途検討します
- JavaScript 以外のモノレポ向けの Bazel、Pants、Buck
- 単一パッケージのリポジトリ設定 → プロジェクトレベルのツールスキルを使用してください
- モノレポ内のサービスのコンテナオーケストレーション → docker-containerization を使用してください
ツール選定の決定木
flowchart TD
A[Monorepo tool needed] --> B{Team size and complexity?}
B -->|Small team, apps-first| C{Need plugin ecosystem?}
B -->|Large org, many teams| D[Rush or Nx]
C -->|No — just fast builds| E[Turborepo]
C -->|Yes — code gen, generators| F[Nx]
D --> G{Microsoft/enterprise patterns?}
G -->|Yes| H[Rush]
G -->|No| I[Nx]
E --> J{Package manager preference?}
J -->|pnpm — recommended| K[pnpm + Turborepo]
J -->|npm or yarn| L[npm/yarn workspaces + Turborepo]
I --> M[Nx Cloud for remote caching]
H --> N[Rush's own cache]
K --> O[Vercel Remote Cache\nor self-hosted]
ツール比較の概要
| ツール | 最適な用途 | リモートキャッシュ | 学習曲線 |
|---|---|---|---|
| Turborepo | アプリ中心、高速なビルド、シンプルな設定 | Vercel/セルフホスト可能 | 低 |
| Nx | ライブラリ中心、コード生成、プラグインエコシステム | Nx Cloud/セルフホスト可能 | 中 |
| Rush | エンタープライズ、Microsoft スタック、厳格な分離 | カスタム | 高 |
| Lerna | レガシー — 移行元 | なし(Turborepo と併用) | 低 |
新規プロジェクトへの推奨:pnpm ワークスペースと Turborepo から始めましょう。高度なコード生成やプロジェクトグラフの可視化が必要になったら、Nx に移行してください。
ワークスペースアーキテクチャの決定木
flowchart TD
A[New package in monorepo?] --> B{Who consumes it?}
B -->|Internal only, not published| C[Internal package:\nno versioning, workspace: protocol]
B -->|Published to npm| D[Published package:\nChangesets, semver, CHANGELOG]
B -->|Shared config only| E[Config package:\neslint-config-*, tsconfig-*]
C --> F{What type?}
F -->|UI components| G[packages/ui]
F -->|Business logic / utilities| H[packages/utils or packages/core]
F -->|Shared types| I[packages/types]
F -->|API client| J[packages/api-client]
D --> K[packages/publishable-name]
A --> L{Is it an application?}
L -->|Yes| M[apps/ directory:\nnext-app, api, docs-site]
依存関係グラフとビルドパイプライン
graph LR
A[apps/web] --> B[packages/ui]
A --> C[packages/utils]
A --> D[packages/types]
E[apps/api] --> C
E --> D
B --> D
F[packages/ui-icons] --> D
B --> F
style A fill:#4a9d9e,color:#fff
style E fill:#4a9d9e,color:#fff
style B fill:#6b7280,color:#fff
style C fill:#6b7280,color:#fff
style D fill:#9ca3af
style F fill:#9ca3af
グラフの読み方:ビルド順序は依存先から依存元へ進みます。packages/types(依存関係なし)が最初にビルドされます。packages/ui は types の後にビルドされます。apps/web は最後にビルドされます。Turborepo と Nx はこのグラフを自動で計算します。タスクの依存関係を定義すると、それに従って実行順序が決まります。
Turborepo の設定
turbo.json — タスクパイプライン
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"inputs": ["src/**/*.tsx", "src/**/*.ts", "package.json", "tsconfig.json"],
"outputs": [".next/**", "!.next/cache/**", "dist/**"]
},
"test": {
"dependsOn": ["^build"],
"inputs": ["src/**/*.ts", "src/**/*.tsx", "**/*.test.ts", "**/*.test.tsx"],
"outputs": ["coverage/**"]
},
"lint": {
"inputs": ["src/**/*.ts", "src/**/*.tsx", ".eslintrc*"]
},
"typecheck": {
"dependsOn": ["^build"]
},
"dev": {
"cache": false,
"persistent": true
}
}
}
主な概念:
^buildは「このパッケージをビルドする前に、すべての依存先をビルドする」という意味ですinputsはキャッシュキーを決定します。inputs の外部での変更ではキャッシュは無効になりませんoutputsはキャッシュに保存され、キャッシュヒット時に復元されますcache: falseは、実行時間の長いタスク(開発サーバー、ウォッチャー)に設定しますpersistent: trueは、終了しないタスクに設定します
タスクの実行
# Run build for all packages
turbo build
# Run only for packages affected by changes since main branch
turbo build --filter=...[origin/main]
# Run for a specific app and its dependencies
turbo build --filter=web...
# Run in parallel across packages
turbo lint typecheck --parallel
# Dry run to see what would execute
turbo build --dry-run
リモートキャッシュ
# Login to Vercel remote cache (free for open source)
npx turbo login
# Link to team/project
npx turbo link
# CI: pass token via environment
TURBO_TOKEN=$TURBO_TOKEN turbo build
セルフホスト可能な代替手段:turbo-remote-cache パッケージ、または Turborepo 2.x に組み込まれている HTTP キャッシュサーバーを使用します。
デプロイ用の Docker プルーニング、スコープを指定したフィルタリング、高度なパイプラインパターンについては、references/turborepo-patterns.md を参照してください。
pnpm ワークスペース
pnpm-workspace.yaml
packages:
- 'apps/*'
- 'packages/*'
- 'tools/*'
package.json のワークスペース依存関係
{
"dependencies": {
"@myorg/ui": "workspace:*",
"@myorg/utils": "workspace:^1.0.0"
}
}
常にローカルバージョンと一致させる内部パッケージには workspace:* を使用します。公開もされており、semver の範囲解決を使用する内部パッケージに限り、workspace:^ を使用します。
ホイスティングの制御
# .npmrc — control hoisting behavior
hoist=true
public-hoist-pattern[]=*eslint*
public-hoist-pattern[]=*prettier*
shamefully-hoist=false # never — breaks encapsulation
shamefully-hoist=true は落とし穴です。すべてのパッケージをどこからでも利用できるようにしますが、カプセル化を損ないます。ツールがこれを必要とする場合は、そのツールの依存関係を修正してください。
バージョニングに Changesets を使用する
# Initialize in your monorepo
npx changeset init
# Create a changeset when making a change
npx changeset
# Prompts: which packages changed, major/minor/patch, description
# Preview what versions will be bumped
npx changeset status
# Bump versions and update changelogs (CI or release branch)
npx changeset version
# Publish to npm
npx changeset publish
.changeset/config.json
{
"$schema": "https://unpkg.com/@changesets/[email protected]/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "restricted",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": ["@myorg/app-web", "@myorg/app-api"]
}
オープンソースパッケージには access: "public" を設定します。ignore には npm に公開しないアプリを列挙します。
CODEOWNERS の完全な設定、ESLint 設定の共有、共有 tsconfig パターンについては、references/workspace-architecture.md を参照してください。
パスベースの CI(変更があったものだけをテスト)
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # required for turbo --filter to work correctly
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- name: Build affected packages
run: pnpm turbo build --filter=...[origin/main]
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
- name: Test affected packages
run: pnpm turbo test --filter=...[origin/main]
--filter=...[origin/main] は、「main と比較してファイルが変更されたパッケージと、それらに依存するすべてのパッケージ(下流の利用側)を対象に実行する」という意味です。
アンチパターン
アンチパターン:タスクキャッシュを使わない(毎回すべてを再ビルドする)
初心者:「turbo buildを実行すると、40個のパッケージのうち1つしか変更していないのに、毎回すべてが再ビルドされます。」
エキスパート:Turborepo は入力(ソースファイル、環境変数、turbo の設定)からハッシュを計算してキャッシュします。入力に変更がなければ、出力はキャッシュから数ミリ秒で復元されます。キャッシュミスの最も一般的な原因は、inputsの宣言がないことです。Turborepo はパッケージディレクトリ全体をハッシュ対象とし、.DS_Store、node_modules、IDE の設定など、頻繁に変更されるファイルも含めます。
検出方法:turbo build --verbosity=2を実行し、パッケージの横に「MISS」と表示されていないか確認します。.turboのキャッシュエントリが存在するか、また実行間でハッシュが一致しているかを確認します。
修正方法:turbo.jsonでinputsを明示的に宣言し、ビルド出力に影響するファイルだけを含めます。キャッシュに保存する内容がわかるよう、outputsも明示的に宣言します。ソース以外のファイルを.gitignoreと.turboignoreに追加します。
LLM による誤り:チュートリアルでは、動作するデモにはinputsとoutputsが不要なため、これらの設定を省略することがよくあります。本番環境のリポジトリでは明示的な宣言が必要であり、そうしなければキャッシュヒット率はほぼ0%のままです。
アンチパターン:ワークスペースパッケージ間の循環依存
初心者:「packages/authがpackages/api-clientからインポートし、packages/api-clientがpackages/authからインポートしています。これは問題ですか?」
エキスパート:はい。循環依存があると、ビルド順を決定できません。循環依存があると、Turborepo はエラーになります。さらに重要なのは、循環依存は設計上の欠陥を示していることです。つまり、関心事が絡み合った2つのパッケージが存在しています。両方から循環を生じさせずにインポートできる3つ目のパッケージに、共有する型やユーティリティを切り出すのが解決策です。
検出方法:
# Turborepo detects cycles and refuses to run
turbo build # "Error: Package graph cycle detected"
# Manual detection with madge
npx madge --circular --extensions ts packages/
修正方法:
Before:
packages/auth → packages/api-client → packages/auth (cycle!)
After:
packages/types (new: shared auth types, no dependencies)
packages/api-client → packages/types
packages/auth → packages/types
packages/auth → packages/api-client (one-way, no cycle)
経緯:これは新しい問題ではありません。循環依存は、npm v1(2010年)以来、JavaScript のパッケージングにおける課題です。モノレポで特に目立つのは、ワークスペースパッケージを使うと、依存関係の方向を意識せずにパッケージ間で簡単にインポートできるためです。
アンチパターン:pnpm でshamefully-hoist=trueを使う
初心者:「CLI ツールがピア依存関係を見つけられません。.npmrcにshamefully-hoist=trueを追加して直します。」
エキスパート:shamefully-hoistを使うと、pnpm は npm/yarn classic のように動作し、すべてのパッケージがフラットなnode_modulesに配置されます。これで目の前の問題は「解決」しますが、pnpm を使う最大の理由である厳密な隔離が損なわれます。正しい解決策は、不足しているピア依存関係を必要とするパッケージに追加するか、public-hoist-patternを使って、そのパッケージだけをホイストすることです。
検出方法:pnpm ワークスペース内にshamefully-hoist=trueを含む.npmrcがある場合です。
参考資料
references/turborepo-patterns.md— turbo.json の設定、リモートキャッシュの構築、デプロイ時の Docker pruning の使用、キャッシュミスのデバッグを行う際に参照してください。references/workspace-architecture.md— パッケージ境界の設計、ESLint/TypeScript 設定の共有、CODEOWNERS の設定、シングルリポジトリからモノレポへの移行計画を行う際に参照してください。