Monorepoとは?npm workspacesを使ったクロスプロジェクトコード共有の完全ガイド
現代のフロントエンドおよびフルスタック開発において、プロダクトが拡大するにつれて、「複数のプロジェクトで同じコードベースを共有する必要がある」という状況によく直面します。例えば、ユーザー向けのメインサイト(Client App)と内部スタッフ向けの管理画面(Admin Panel)などです。これらは独立して稼働しますが、同じUIコンポーネントライブラリ、API呼び出しロジック、または型定義を共有することがよくあります。
もし両方のプロジェクトに同じコードをコピペしてしまうと、将来ロジックを変更する際に、エンジニアは複数のプロジェクトで何度も変更作業を行わなければならず、見落としやバージョンの不一致が生じるリスクが高まります。この問題を解決するために、**Monorepo(モノレポ )**アーキテクチャが登場しました。そして、現在のNode.jsエコシステムにおいて、npm workspaces は最も導入しやすいツールの1つです。
Monorepoアーキテクチャとは?

Monorepo(Monolithic Repository)とは、複数の異なるプロジェクトやパッケージを、すべて単一のGitリポジトリ内で管理することを指します。これと相対するのが従来のPolyrepo(Multi-repo)であり、各プロジェクトが独立したリポジトリを持つ構成です。
Monorepoの主なメリット
- 信頼できる唯一の情報源 (Single Source of Truth):すべてのコードが同じツリー構造の下にあり、チームメンバーが常に一貫したコードベースを参照できるようになります。
- コードの共有が簡単:クロスプロジェクトで共有モジュールを参照する際、ローカルでのシンボリックリンク (Symlink) を使うだけで済みます。テストのためにパッケージをいちいち npm registry に公開する必要はありません。
- 依存関係の一貫性:サードパーティの依存パッケージ(ReactやLodashなど)をルートディレクトリに巻き上げ (Hoist) することで、すべてのサブプロジェクトで使用されるパッケージバージョンを完全に一致させ、バージョンの競合を防ぎ、ストレージのスペースを節約できます。
- 大規模なリファクタリングが容易:共有モジュールのAPIが変更された場合、そのモジュールに依存するすべてのプロジェクトが同じリポジトリ内にあるため、エンジニアは一度に変更を行い、TypeScriptコンパイラを使って潜在的なエラーをすべて検出できます。
npm workspacesについて
npm v7 バージョン以降、npm は公式に Workspaces のサポートを組み込みました。これは、同じローカルファイルシステム内の複数のパッケージの依存関係を管理するためのCLIネイティブ機能を提供します。ルートディレクトリの package.json を設定するだけで、npm は自動的にサブプロジェクトの依存関係を整理し、相互参照できるようにしてくれます。
実践チュートリアル:npm workspacesを使ってコードを共有する方法
具体的な例を通して、project-a、project-b、および共有モジュール shared-utils を含む Monorepo を構築する方法を説明します。
ステップ1:ルートディレクトリの作成と初期化
まず、Monorepo のルートディレクトリとして新しいフォルダを作成し、プロジェクトを初期化します。
mkdir my-monorepo
cd my-monorepo
npm init -y
次に、生成されたルートディレクトリの package.json に手動で workspaces フィールドを追加します。これにより、この Monorepo がどのディレクトリにサブプロジェクトを含んでいるかを宣言します。通常、プロジェクトは apps(アプリケーション)と packages(共 有モジュール)に分類します。
{
"name": "my-monorepo",
"private": true,
"workspaces": [
"apps/*",
"packages/*"
]
}
注意:誤ってワークスペース全体をパブリックのnpmレジストリに公開しないように、ルートディレクトリの
package.jsonには必ず"private": trueを設定してください。
ステップ2:共有モジュール (Shared Package) の作成
それでは、共有ロジックを配置するパッケージを作成しましょう。ルートディレクトリ配下に packages/shared-utils フォルダを作成します。
mkdir -p packages/shared-utils
cd packages/shared-utils
npm init -y
packages/shared-utils/package.json を編集します。特に "name" フィールドに注意してください。これが他のプロジェクトからこのモジュールを参照する際に使用される名前になります。
{
"name": "@my-org/shared-utils",
"version": "1.0.0",
"main": "index.js"
}
次に、そのフォルダ内に index.js を作成し、共有したい関数を記述します。
// 共有の日付フォーマット関数
function formatDate(date) {
return new Intl.DateTimeFormat('ja-JP', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
}).format(date);
}
// 共有の足し算関数
function add(a, b) {
return a + b;
}
module.exports = {
formatDate,
add
};
ステップ3:2つのアプリケーションプロジェクトの作成
ルートディレクトリに戻り、apps/ ディレクトリ配下に2つの独立したプロジェクトを作成します(ここでは簡略化のため、基本的な Node.js プロジェクトを初期化しますが、実務では Next.js や React、Express などのプロジェクトになります)。
mkdir -p apps/project-a
mkdir -p apps/project-b
# project-a の初期化
cd apps/project-a
npm init -y
# project-b の初期化
cd ../project-b
npm init -y
それぞれ apps/project-a/package.json と apps/project-b/package.json の "name" を "project-a" と "project-b" に変更します。