
モノレポ vs マルチレポ:選定基準と運用の実務ガイド
「複数のサービスやパッケージを開発する際、リポジトリをどう分けるべきか?」——この問いは受託開発・自社開発の現場で頻繁に浮上します。モノレポ(Monorepo)とマルチレポ(Polyrepo)にはそれぞれメリット・デメリットがあり、プロジェクトの特性やチーム規模によって最適解は変わります。
本記事では、モノレポ・マルチレポの選定基準、主要ツール(Turborepo / Nx / pnpm workspace)の比較、段階的な移行手順、運用の実務ポイントを具体例とともに解説します。
1. モノレポとマルチレポの定義と基本特性
モノレポ(Monorepo)
単一リポジトリに複数のプロジェクト・パッケージを格納する構成です。Google・Meta・Microsoft などの大規模組織で採用されている実績があります。
my-monorepo/
├── apps/
│ ├── web/ # Next.js アプリ
│ ├── admin/ # 管理画面
│ └── mobile/ # React Native アプリ
├── packages/
│ ├── ui/ # 共通 UI コンポーネント
│ ├── utils/ # ユーティリティ関数
│ └── api-client/ # API クライアント
└── package.json
主な特徴:
- コード共有が容易(型定義・ユーティリティ・UI コンポーネント)
- 一括での依存関係更新・リファクタリングが可能
- 統一された CI/CD・リント・テスト設定
- 全体のビルド時間が長くなる可能性
マルチレポ(Polyrepo)
プロジェクトごとに独立したリポジトリを持つ構成です。従来の開発スタイルで、シンプルかつ理解しやすいアプローチです。
organization/
├── web-app/ # 独立リポジトリ
├── admin-app/ # 独立リポジトリ
├── mobile-app/ # 独立リポジトリ
├── ui-library/ # npm パッケージとして公開
└── utils-library/ # npm パッケージとして公開
主な特徴:
- プロジェクト間の独立性が高い
- リポジトリごとに異なる技術スタック・リリースサイクルが可能
- アクセス権限の管理が容易
- コード共有には npm パッケージ化が必要
- 依存関係の更新が分散しやすい
2. 選定基準:どちらを選ぶべきか
チェックリスト形式の判断基準
以下の表で、プロジェクトの特性とマッチする構成を確認してください。
| 判断基準 | モノレポ向き | マルチレポ向き | |---------|------------|-------------| | チーム規模 | 5〜50 名の中規模チーム | 3 名以下の小規模 or 100 名以上の大規模 | | プロジェクト数 | 3〜10 個の関連サービス | 1〜2 個 or 完全に独立した 10 個以上 | | コード共有頻度 | 共通ロジック・UI が多い | ほとんど共有しない | | リリースサイクル | 同期的(週次・月次一斉リリース) | 非同期的(各サービス独立) | | 技術スタック | TypeScript / React 系で統一 | Java・Go・Python など混在 | | CI/CD 時間 | 10 分以内に収まる(または高速化可能) | 既に 15 分以上かかっている | | チーム体制 | フルスタック志向・横断的協力 | 専門チームごとの縦割り |
具体的な選定フローチャート
複数のプロジェクトを持つか?
├─ No → マルチレポ(単一プロジェクトなら不要)
└─ Yes → プロジェクト間でコード共有が頻繁か?
├─ Yes → 同じ技術スタックか?
│ ├─ Yes → モノレポ推奨
│ └─ No → マルチレポ + 共通パッケージ化
└─ No → 独立したリリースサイクルか?
├─ Yes → マルチレポ推奨
└─ No → モノレポを検討
3. モノレポツールの比較:Turborepo / Nx / pnpm workspace
主要ツールの機能比較表
| 機能 | Turborepo | Nx | pnpm workspace | |------|-----------|----|-----------------| | タスクキャッシュ | ◎(ローカル・リモート) | ◎(Nx Cloud) | △(手動設定) | | 依存関係グラフ | ○ | ◎(可視化ツール付き) | × | | 増分ビルド | ◎ | ◎ | △ | | 学習コスト | 低 | 中 | 低 | | プラグイン | 少ない | 豊富(React/Angular/Next.js など) | なし | | TypeScript パス解決 | 手動設定 | 自動 | 手動設定 | | リモートキャッシュ | Vercel 統合 | Nx Cloud | 非対応 | | 最適な用途 | Next.js / Vercel 環境 | 大規模・複雑な依存関係 | シンプルな構成 |
Turborepo を使った最小構成
// turbo.json
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "dist/**"]
},
"test": {
"dependsOn": ["^build"],
"cache": false
},
"lint": {
"outputs": []
}
}
}
// package.json(ルート)
{
"name": "my-monorepo",
"private": true,
"workspaces": ["apps/*", "packages/*"],
"scripts": {
"build": "turbo run build",
"test": "turbo run test",
"lint": "turbo run lint"
},
"devDependencies": {
"turbo": "^1.10.0"
}
}
ポイント:
dependsOn: ["^build"]で依存パッケージを先にビルドoutputsでキャッシュ対象を明示- Vercel にデプロイする場合、自動的にリモートキャッシュが有効化
4. モノレポ運用の実務パターン
パターン 1:共通パッケージの設計
TypeScript の型定義・UI コンポーネント・API クライアントを共通化します。
// packages/types/src/index.ts
export interface User {
id: string;
email: string;
name: string;
}
export interface ApiResponse<T> {
data: T;
error?: string;
}
// apps/web/src/app/page.tsx
import { User } from '@my-org/types';
import { Button } from '@my-org/ui';
export default function Home() {
const user: User = { id: '1', email: 'test@example.com', name: 'Test' };
return <Button>{user.name}</Button>;
}
package.json の設定:
// apps/web/package.json
{
"name": "web",
"dependencies": {
"@my-org/types": "workspace:*",
"@my-org/ui": "workspace:*"
}
}
パターン 2:CI/CD での変更検出最適化
変更があったパッケージだけをビルド・テストすることで CI 時間を短縮します。
# .github/workflows/ci.yml
name: CI
on:
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Turborepo の変更検出に必要
- uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- run: pnpm install
# 変更されたパッケージだけテスト
- run: pnpm turbo run test --filter=...[origin/main]
# リモートキャッシュを有効化(Vercel の場合)
- run: pnpm turbo run build --token=${{ secrets.TURBO_TOKEN }}
--filter オプションの活用:
| コマンド | 動作 |
|---------|------|
| --filter=web | web アプリのみ |
| --filter=...web | web とその依存パッケージ |
| --filter=...[origin/main] | main ブランチからの変更箇所のみ |
パターン 3:バージョニングと Changesets
複数パッケージの変更を追跡し、セマンティックバージョニングを自動化します。
# Changesets のセットアップ
pnpm add -D @changesets/cli
pnpm changeset init
# PR 作成時に変更内容を記録
pnpm changeset
# → 対話式で変更の種類(major/minor/patch)を選択
# .github/workflows/release.yml
name: Release
on:
push:
branches:
- main
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
- run: pnpm install
- run: pnpm build
# Changesets でバージョン更新・npm publish
- name: Create Release Pull Request
uses: changesets/action@v1
with:
publish: pnpm changeset publish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
5. マルチレポ運用の実務パターン
パターン 1:共通ロジックの npm パッケージ化
プライベート npm レジストリ(GitHub Packages / Verdaccio)を使う方法です。
// @my-org/utils の package.json
{
"name": "@my-org/utils",
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"publishConfig": {
"registry": "https://npm.pkg.github.com"
}
}
# .github/workflows/publish.yml
name: Publish Package
on:
push:
tags:
- 'v*'
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
registry-url: 'https://npm.pkg.github.com'
- run: npm ci
- run: npm run build
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
パターン 2:依存関係の更新自動化(Renovate / Dependabot)
各リポジトリで依存パッケージのバージョンを自動更新します。
// renovate.json(各リポジトリに配置)
{
"extends": ["config:base"],
"packageRules": [
{
"matchPackagePatterns": ["@my-org/*"],
"groupName": "my-org packages",
"automerge": true,
"automergeType": "branch"
}
],
"schedule": ["before 10am on monday"]
}
6. モノレポへの段階的移行手順
既存のマルチレポ構成からモノレポに移行する際のリスクを抑えた手順を示します。
ステップ 1:共通パッケージの切り出し
# 新規モノレポの作成
mkdir my-monorepo && cd my-monorepo
pnpm init
pnpm add -D turbo
mkdir -p packages/ui packages/utils
# 既存リポジトリから共通コードをコピー
cp -r ../old-web-app/src/components packages/ui/src
cp -r ../old-web-app/src/utils packages/utils/src
ステップ 2:アプリケーションの段階的移行
# apps ディレクトリに既存プロジェクトを Git submodule として追加
git submodule add https://github.com/my-org/web-app apps/web
# または直接移動(履歴を保持する場合)
git subtree add --prefix=apps/web https://github.com/my-org/web-app main
ステップ 3:依存関係の書き換え
// apps/web/package.json(移行前)
{
"dependencies": {
"@my-org/ui": "^1.0.0" // npm から取得
}
}
// apps/web/package.json(移行後)
{
"dependencies": {
"@my-org/ui": "workspace:*" // ローカル参照
}
}
ステップ 4:CI/CD の並行運用
既存のマルチレポ CI とモノレポ CI を一定期間並行稼働させ、動作確認します。
# モノレポ側の CI
jobs:
test-web:
steps:
- run: pnpm turbo run test --filter=web
# 既存リポジトリの CI と結果を比較
7. よくある失敗パターンと対処法
失敗パターン 1:モノレポ肥大化による CI 時間爆発
症状: ビルド時間が 30 分以上かかる
対処法:
- Turborepo / Nx のリモートキャッシュを有効化
--filterで変更箇所だけビルド- 大きなアプリは別リポジトリに分離
# リモートキャッシュの確認
turbo run build --dry-run --summarize
失敗パターン 2:依存関係の循環参照
症状: packages/ui が packages/utils に依存し、packages/utils が packages/ui に依存
対処法:
# 依存グラフの可視化
pnpm turbo run build --graph=graph.html
# または Nx の場合
nx graph
循環が見つかったら、共通部分を新しいパッケージ packages/core に切り出します。
失敗パターン 3:型解決の遅延
症状: IDE で共通パッケージの型が認識されない
対処法:
// tsconfig.json(ルート)
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@my-org/*": ["packages/*/src"]
}
},
"references": [
{ "path": "./packages/ui" },
{ "path": "./packages/utils" }
]
}
8. まとめ:プロジェクトに応じた最適解を選ぶ
| 状況 | 推奨構成 | 理由 | |------|---------|------| | スタートアップ MVP | マルチレポ | シンプルさ優先、プロジェクト数が少ない | | 5〜10 人の Web サービス開発 | モノレポ(Turborepo) | コード共有・統一設定のメリット大 | | 20 人以上の複雑な依存関係 | モノレポ(Nx) | 依存グラフ可視化・プラグインが有用 | | 技術スタック混在 | マルチレポ + 共通パッケージ | 言語ごとに最適化、共通部分だけパッケージ化 | | 完全に独立したサービス群 | マルチレポ | リリースサイクル・チーム体制が異なる |
実務で迷ったときの判断基準
- チームの技術レベル:モノレポツールの学習コストを許容できるか
- CI/CD 時間:現在の CI が 10 分以内に収まっているか
- コード共有頻度:週 1 回以上共通ロジックを更新しているか
- リリース頻度:各サービスが独立してリリースする必要があるか
どちらが絶対的に優れているわけではありません。プロジェクトのフェーズやチーム特性に合わせて選択し、必要に応じて段階的に移行する柔軟性を持つことが重要です。
Yureate へのお問い合わせ
Yureate では、モノレポ構成の設計支援・既存システムのリファクタリング・CI/CD パイプライン最適化を行っています。リポジトリ構成の見直しや移行支援が必要な場合は、お気軽にご相談ください。
