モノレポ(monorepo、モノリポとも表記)とは、複数のアプリケーションやライブラリを1つのリポジトリに置き、依存関係を明示したうえで一緒に管理する方式です。対になるのは、プロジェクトごとにリポジトリを分けるマルチレポ(ポリレポ)です。
この記事では、定義と違いを整理したあと、pnpm workspace での最小構成とGitの大規模リポジトリ機能を実際のコマンドで示し、採用すべきでない条件まで判断材料として並べます。
まとめ:モノレポの定義と採用判断の要点
- 定義:複数プロジェクトを1リポジトリに置き、プロジェクト間の依存を明示して管理する方式。コードを1か所に置くだけでは単なる同居です。
- 最大の利点:共有ライブラリと利用側を1コミット・1プルリクエストで同時に変更できること。
- 最大の弱点:Gitの読み取り権限をディレクトリ単位で分けられないこと、変更のたびに全体をビルドするとCIが遅くなること。
- 向く条件:同じ言語・同じリリースサイクルで、共有コードを頻繁に更新するチーム。
- 向かない条件:取引先ごとにソースの閲覧範囲を分ける必要がある、プロジェクト間で共有するコード・設定・API契約がほとんど無い、独立したOSSとして外部貢献を受けたい。
「リポジトリが増えて管理が大変」という理由だけで移行すると、CIとGitの重さに別の手間を払うことになります。以下で根拠と手順を順に示します。
モノレポの定義とモノリス・マルチレポとの違い
「複数プロジェクト+明確な依存関係」という定義
Nxの開発元が運営する解説サイト monorepo.tools は、モノレポを「A monorepo is a single repository containing multiple distinct projects, with well-defined relationships.」と定義しています。後半の「well-defined relationships(明確に定義された関係)」が要点で、同サイトは関係の定義がない単なる同居を「code colocation」と呼び、モノレポと区別しています。
典型的な構成は、ルートに apps/(デプロイする単位)と packages/(共有ライブラリ)を置く形です。フロントエンド・バックエンド・共通の型定義を1リポジトリに入れ、フロントエンドが packages/types をバージョン番号でなくワークスペース参照で取り込む、というのが分かりやすい例です。
モノリス・マルチレポ(ポリレポ)との比較
| 観点 | モノレポ | マルチレポ(ポリレポ) | モノリス |
|---|---|---|---|
| 何の分け方か | リポジトリ | リポジトリ | アプリケーション構造 |
| リポジトリ数 | 1 | プロジェクトごと | 無関係 |
| デプロイ単位 | プロジェクトごとに分離可 | リポジトリごと | 1つ |
| 共有コードの参照 | ワークスペース参照 | パッケージ公開+バージョン指定 | 同一コード内の呼び出し |
| 権限の単位 | リポジトリ全体 | リポジトリごと | 無関係 |
混同されやすいのがモノリスです。モノリスは1つのアプリケーションとしてまとめてデプロイする設計の話で、モノレポはソースコードの置き場所の話です。モノレポの中に独立してデプロイするマイクロサービスを並べることもできます。設計側の選択はマイクロサービスとモノリスの違いで整理しています。「モノレポの逆」を聞かれたら、答えはモノリスではなくマルチレポ(ポリレポ)です。
モノレポのメリット:同時変更と設定の一元化
共有ライブラリと利用側の同時変更
マルチレポで共有ライブラリの関数シグネチャを変えると、ライブラリを公開してバージョンを上げ、利用側の各リポジトリで依存を更新してプルリクエストを出す、という多段の作業になります。途中の状態では、古いバージョンを使うリポジトリと新しいバージョンを使うリポジトリが混在します。
モノレポなら、ライブラリの変更と全利用箇所の修正を1つのプルリクエストに入れられます。レビュー担当者は影響範囲を1画面で確認でき、CIも変更後の組み合わせで一度に検証できます。GoogleがCACMの論文で挙げた利点にも、atomic changes(アトミックな変更)と large-scale refactoring(大規模リファクタリング)が含まれています。
依存バージョンとツール設定の一元管理
ESLint・TypeScript・テストランナーの設定や、外部ライブラリのバージョンをルートで共有できます。pnpmにはワークスペース全体の依存バージョンを1か所に定義する catalog 機能もあります。リポジトリが10本あれば10本それぞれで行っていた依存更新が、1回のプルリクエストで済みます。
ただし、バージョンを1か所で揃える運用にすると、その依存を使う全プロジェクトが同じタイミングで更新の影響を受けます。モノレポでもプロジェクトごとに別バージョンを持つことはできますが、揃える利点と引き換えに「更新の足並みをそろえる調整」が発生する点は、次のデメリットと表裏一体です。
モノレポのデメリット:Gitの重さ・CI時間・権限
リポジトリ肥大化とGit操作の遅延
ファイル数と履歴が増えると、git clone・git status・git log が目に見えて遅くなります。Googleは独自のバージョン管理システムPiperを、MetaはMercurialを拡張したSaplingを、MicrosoftはGitに仮想ファイルシステムを組み合わせたVFS for Gitを作ってこの問題に対処しました。そこまでの規模でなければ、まずGit標準の機能で負荷を減らせます。partial clone は取得するファイル内容の量を、sparse-checkout は作業ツリーに展開するファイル数を減らすもので、どちらも後述の手順で使えます。
変更のたびに全体をビルドするCI
何も工夫しないと、READMEを1行直しただけでも全プロジェクトのビルドとテストが走ります。対策は2段階です。まずCIの起動条件をパスで絞り(GitHub Actionsなら on.push.paths。タグのpushではパスフィルターが評価されない点に注意)、次にビルドツールの「影響範囲検出」とキャッシュで、変更したパッケージとそれに依存するパッケージだけを実行します。後者の実例は構成例の章で示します。
Gitリポジトリの読み取り権限の制約
GitHubやGitLabの権限はリポジトリ単位で、クローンできる人はリポジトリ内の全ファイルを読めます。GitHubの CODEOWNERS はディレクトリごとにレビュー担当者を指定する仕組みで、読み取りを制限する機能ではありません。受託開発で顧客ごとにソースの閲覧範囲を分ける必要がある場合、モノレポは構造的に合いません。
Google・Meta・Microsoftのモノレポ規模と専用ツール
「モノレポはGoogleがやっているから正しい」という紹介をよく見かけますが、3社とも規模の拡大に合わせて専用のツールへ投資しています。GoogleとMetaは独自のバージョン管理システムを作り、MicrosoftはGitを基盤に仮想ファイルシステムなどの拡張を加えました。
| 企業 | 公表された規模 | 時点 | 専用ツール |
|---|---|---|---|
| 10億ファイル・20億行・86TB | 2015年1月 | Piper・CitC・Bazel | |
| Meta | ファイル・コミット・ブランチ各数千万 | 2022年11月 | Sapling・Buck2 |
| Microsoft(Windows) | 約350万ファイル・約300GB | 2017年 | VFS for Git・Scalar |
Googleの10億ファイルには、リリースブランチへの複製や最新版で削除済みのファイル、設定・ドキュメントなども含まれ、固有のソースファイルは900万です。86TBはリリースブランチを除いた非圧縮の容量です。
GoogleのCACM論文(Potvin・Levenberg、2016年)によると、このリポジトリを2万5,000人超の開発者が共有し、典型的な1日に人間が1万6,000件、自動化システムが2万4,000件の変更をコミットしていました。同じ論文は欠点の筆頭に「開発と実行のためのツールを作り、スケールさせる必要があること」を挙げています。Metaは2022年11月15日に、Mercurialの拡張から始まったSaplingのクライアントを、2023年4月6日にビルドシステムBuck2を公開しました。MicrosoftはWindowsのリポジトリを GVFS 付きのGitへ移行し、その成果は一般のGitに還元され、ScalarはGit 2.38.0で本体に取り込まれています(手元の Apple Git 2.50.1 には scalar コマンドが同梱されていませんでした)。
数百人規模までのチームがこれらの事例から持ち帰るべきなのは「モノレポが正しい」ではなく、「規模が大きくなるほど専用ツールへの投資が前提になる」という事実です。
モノレポ構成の作り方:pnpm workspaceの最小例
apps・packagesを分けるディレクトリ構成
JavaScript/TypeScriptなら、パッケージマネージャーのワークスペース機能だけでモノレポを始められます。以下は、共有ライブラリ @acme/ui をアプリ web が使う最小構成です。
.
├── pnpm-workspace.yaml
├── package.json
├── apps/
│ └── web/
│ ├── package.json
│ └── index.js
└── packages/
└── ui/
├── package.json
└── index.js
pnpm-workspace.yamlとworkspace:プロトコル
ルートの pnpm-workspace.yaml でワークスペースに含めるディレクトリを指定し、利用側の package.json では依存を workspace:* で書きます。
# pnpm-workspace.yaml
packages:
- "apps/*"
- "packages/*"
# apps/web/package.json
{
"name": "web",
"private": true,
"dependencies": { "@acme/ui": "workspace:*" },
"scripts": { "start": "node index.js" }
}
# package.json(ルート)
{ "name": "root", "private": true }
# packages/ui/package.json
{ "name": "@acme/ui", "version": "1.0.0", "main": "index.js" }
# packages/ui/index.js
exports.hello = () => "hello from ui";
# apps/web/index.js
console.log(require("@acme/ui").hello());
$ pnpm install
$ pnpm --filter web start
hello from ui
pnpm 11.15.1 で pnpm install を実行すると、apps/web/node_modules/@acme/ui は ../../../../packages/ui へのシンボリックリンクになり、npmレジストリへ公開しなくても変更が即座に反映されました。workspace: を付けた依存はローカルのワークスペースパッケージ以外へ解決されず、該当パッケージが無ければインストールが失敗するので、レジストリ上の同名パッケージを誤って取り込む事故も防げます。pnpm自体の仕組みはpnpmの共有ストアとモノレポ運用で解説しています。
変更パッケージと依存元に絞った差分実行
pnpmの --filter は、Gitの差分を起点に対象パッケージを絞れます。...[HEAD~1] は「直前のコミットから変更があったパッケージと、それに依存するパッケージ」を意味します。
$ pnpm --filter "...[HEAD~1]" exec pwd
/private/tmp/mr/packages/ui
/private/tmp/mr/apps/web
packages/ui だけを変更したコミットで実行した結果です。変更していない web も、ui に依存しているため対象に入りました。CIでは pnpm --filter "...[origin/main]" test のようにベースブランチとの差分を指定すると、影響のあるパッケージだけをテストできます(各パッケージに test スクリプトがある前提です)。GitHub Actionsの actions/checkout は既定で1コミットしか取得しないため、fetch-depth: 0 を指定するか比較先のブランチを別途 fetch しておかないと、origin/main が見つからず差分を計算できません。これにタスク結果のキャッシュや並列実行を足したものが、NxやTurborepoなどの専用ツールです。
大規模化への備え:sparse-checkout・partial clone・CODEOWNERS
必要なディレクトリだけを取り出すsparse-checkout
Git標準の partial clone(--filter=blob:none)はファイルの中身を必要になるまでダウンロードせず、sparse-checkout は作業ツリーに展開するディレクトリを限定します。組み合わせると、巨大なモノレポでも担当部分だけを手元に置けます。
git clone --filter=blob:none --sparse https://example.com/acme/mono.git
cd mono
git sparse-checkout add apps/web packages/ui
Git 2.50.1 で試すと、--sparse 直後の作業ツリーにはルート直下のファイル(package.json など)だけが展開され、add したディレクトリが追加されました。cone モードではルート直下のファイルは常に展開される点に注意してください。リポジトリが育ってきたら、git maintenance start でリポジトリ最適化のバックグラウンド実行を登録しておくと、日常のGit操作が遅くなりにくくなります。
レビュー担当をディレクトリで割り当てるCODEOWNERS
チームが増えると「誰がこのディレクトリの変更を承認するか」を決める必要があります。GitHubでは .github/CODEOWNERS にパスと担当チームを書き、ブランチ保護でコードオーナーのレビューを必須にします。
# .github/CODEOWNERS
/apps/web/ @acme/frontend
/packages/ui/ @acme/design-system
/infra/ @acme/platform
前述のとおり、これは承認の割り当てであり閲覧制限ではありません。CIの起動条件をパスで絞る書き方はGitHub Actionsのトリガー(on)とpathsフィルタを参照してください。
既存リポジトリを履歴ごとモノレポへ統合する手順
既存の複数リポジトリをまとめるとき、単純にファイルをコピーすると git blame と git log の履歴が失われます。git subtree add --prefix=services/b は履歴を取り込みますが、元のコミットはルート直下のパスのままなので、Git 2.50.1 で git log -- services/b を実行すると取り込みコミット1件しか表示されませんでした。
パス単位の履歴を残すには、取り込む側のリポジトリを先に git-filter-repo でサブディレクトリへ書き換えてからマージします。
# git-filter-repo は別途インストールが必要(pip install git-filter-repo など)
# URL・ディレクトリ名・ブランチ名(main)は自分の環境に置き換える
# 取り込むリポジトリの複製側で、全履歴を services/b 配下へ移す
git clone https://example.com/acme/service-b.git service-b-tmp
cd service-b-tmp
git filter-repo --to-subdirectory-filter services/b
# モノレポ側で、無関係な履歴として取り込む
cd ../mono
git remote add service-b ../service-b-tmp
git fetch service-b
git merge --allow-unrelated-histories service-b/main -m "import service-b"
この手順では git log -- services/b/README.md に元の2コミットがそのまま表示されました。書き換えるのは複製側だけなので、元リポジトリの履歴とIssueからのコミットリンクはそのまま残りますが、モノレポに取り込んだ側のコミットハッシュはすべて変わります。新旧ハッシュの対応表は複製側の .git/filter-repo/commit-map に出力されるので、移行後に古いハッシュを追えるよう保存しておきます。元リポジトリは更新が分岐しないようアーカイブして読み取り専用にするのが安全です。
モノレポ管理ツールの選び方:Nx・Turborepo・Bazelほか
| ツール | 最新版(2026-09-26) | 主な対象 | 選定時の観点 |
|---|---|---|---|
| pnpm workspace | 12.6.0 | JS/TS | 依存管理 |
| Turborepo | 2.11.4 | JS/TS | タスク実行・キャッシュ |
| Nx | 23.2.1 | JS/TS中心+他言語 | 依存制約・コード生成 |
| Lerna | 10.0.1 | JS/TS | バージョン管理・npm公開 |
| Rush | 5.179.0 | JS/TS | 多数パッケージの運用 |
| moon | 2.5.5 | 多言語 | 多言語のタスク管理 |
| Bazel | 9.2.0 | 多言語 | 多言語の再現可能なビルド |
版はnpmレジストリの latest タグと、Bazel・moonはGitHub Releasesの Latest 指定で確認した値です(pnpmは12.7.0が2026年9月25日に公開済みですが、npmの latest タグは12.6.0のままでした)。選び方は言語構成で決まります。JS/TSだけならまずpnpm workspaceで始め、CIが遅くなったらキャッシュ付きのタスクランナーを足すのが最も手戻りが少ない順番です。設定の少なさを優先するならTurborepoのturbo.json設定とキャッシュ共有、プロジェクト間の依存制約やコード生成まで欲しいならNx monorepoの機能と導入手順が候補です。
Lernaは2022年5月6日付でNx開発元のNrwlへ管理が移管されています。JavaとGoとPythonが混在するような多言語モノレポでは、言語ごとのビルドを統一的に扱えるBazelやmoonが候補になりますが、Bazelを選ぶ場合は、全ディレクトリへの BUILD ファイルの記述、言語ごとのルールの選定と更新、既存ビルドからの移行工数、それらを保守する担当者の確保まで見積もりに入れてください。
モノレポを採用すべきでないケース
- 閲覧範囲を分ける必要がある:受託案件で顧客ごとにソースを隔離する、協力会社に一部だけ見せる。Gitの権限がリポジトリ単位である以上、分けるならリポジトリを分けるしかありません。
- プロジェクト間で共有するものがほとんど無い:共有ライブラリも、API契約(型定義やスキーマ)も、Lintやビルドの設定も共通化していないなら、モノレポの利点である同時変更がほぼ発生せず、CIとGitの重さのほうが目立ちます。
- 共有依存の更新責任を決められない:月1回リリースの業務システムと毎日デプロイするWebを同居させる場合、リリース自体はプロジェクトごとに独立させられます。問題になるのは、共有ライブラリの破壊的変更を誰がいつ全利用側へ反映するかを決められない体制のときです。
- 外部貢献を受けるOSSとして切り出したい:コントリビューター向けに、社内プロジェクトと混在したリポジトリでのビルド手順やCIの説明が必要になり、IssueやプルリクエストもOSSと社内の案件が同じ場所に並びます。
判断に迷ったら、「過去3か月で、2つ以上のリポジトリにまたがる変更を何回出したか」を数えてください。ほぼゼロならマルチレポのままで困っていないということです。
モノレポに関するよくある質問
モノレポの対義語は何ですか?
マルチレポ(multi-repo)、またはポリレポ(polyrepo)です。プロジェクトごとにリポジトリを分ける方式を指します。モノリスはアプリケーション設計の用語なので対義語ではありません。
モノリポとモノレポは違うものですか?
同じものです。英語の monorepo(monolithic repository の略)をカタカナにしたときの表記ゆれで、「モノリポジトリ」と書かれることもあります。
モノレポにするとデプロイもまとめて行う必要がありますか?
必要ありません。ソースの置き場所が1つになるだけで、デプロイはアプリケーションごとに別々のパイプラインで行えます。CIでパスや影響範囲を絞れば、変更したアプリだけをデプロイできます。
数人の小さなチームでもモノレポにする意味はありますか?
フロントエンドとバックエンドで型定義やバリデーションを共有しているなら意味があります。専用ツールは不要で、pnpmやnpmのワークスペース機能だけで始められます。共有コードが無いなら急いで移行する理由はありません。
GitHubでモノレポを運用するときに最初に設定すべきことは?
CODEOWNERSでディレクトリごとのレビュー担当を決めることと、GitHub Actionsのワークフローをパスで絞ることの2つです。CODEOWNERSを置くと、プルリクエストが担当ディレクトリを変更したときにGitHubが担当チームへ自動でレビューを依頼するので、誰に見てもらうかを毎回決める手間が減ります。パスや影響範囲で実行対象を絞らない場合は、変更のたびにワークフローが定義する全ジョブが実行されます。