T-CREATOR

<div />

TypeScriptでモノレポ管理をセットアップする手順 プロジェクト分割と依存関係制御の実践

2026年1月10日
TypeScriptでモノレポ管理をセットアップする手順 プロジェクト分割と依存関係制御の実践

TypeScript でモノレポを構築する際、「とりあえず Yarn Workspaces を入れて動いた」で満足していませんか。実際の開発では、ビルド順序が壊れたり、変更していないパッケージまで再ビルドされたり、CI で 10 分以上かかったりと、運用で破綻するケースが多発します。

本記事は、TypeScript モノレポのセットアップから運用まで、実務で問題にならない設計判断を整理したものです。Project References を中心に、tsconfig.json の階層化、ビルド順序の制御、差分ビルドによる CI/CD 高速化まで、実際に検証した内容をもとに解説します。

「初回ビルドは通るけど 2 回目で壊れる」「型チェックは通るのにビルドが失敗する」といった問題を避けるため、静的型付けの恩恵を最大限に活かしつつ、運用で破綻しないセットアップ手順を提示します。

検証環境

  • OS: macOS Sequoia 15.2
  • Node.js: 22.21.0 (LTS)
  • TypeScript: 5.9.3
  • 主要パッケージ:
    • yarn: 4.12.0
    • turbo: 2.3.3
  • 検証日: 2026 年 1 月 10 日

背景:TypeScript モノレポが必要になる実務場面

モノレポ管理とは何か

この章では、モノレポが解決する実務上の課題と、TypeScript との相性について理解できます。

モノレポ(Monorepo)とは、複数の関連プロジェクトを単一リポジトリで管理する手法です。フロントエンド、バックエンド、共通ライブラリを同じリポジトリに配置し、依存関係を明示的に管理します。

対義語はマルチレポ(Polyrepo)で、各プロジェクトを個別リポジトリで管理する従来の方法です。

bashmonorepo/
├── packages/
│   ├── web/        # Next.js アプリ
│   ├── api/        # Express API
│   └── shared/     # 共通型・ユーティリティ
├── package.json
└── tsconfig.json

TypeScript で型を共有する必要性

TypeScript プロジェクトでは、フロントエンドとバックエンドで同じ型定義を使いたい場面が頻繁に発生します。たとえば API レスポンスの型、データベースモデルの型、バリデーションスキーマなどです。

マルチレポでは、これらの型を npm パッケージとして公開し、各プロジェクトでインストールする必要がありました。しかし、型定義 1 つ変更するたびに「パッケージ公開 → バージョンアップ → 各リポジトリで yarn upgrade」という手順を踏むのは非効率です。

モノレポでは、共通の型定義を packages/shared に配置し、他のパッケージから直接参照できます。型の変更は即座に全体に反映され、静的型付けの恩恵を最大限に活かせます。

typescript// packages/shared/src/types.ts
export interface User {
  id: string;
  name: string;
  email: string;
}

// packages/api/src/user.ts
import { User } from "@myapp/shared";

// packages/web/src/components/UserProfile.tsx
import { User } from "@myapp/shared";

実務で起きる依存関係の混乱

実際の開発では、複数プロジェクトで同じライブラリの異なるバージョンを使ってしまうことがあります。検証中、プロジェクト A は React 18.2、プロジェクト B は React 18.3 を使っており、型定義の不整合でビルドエラーが発生しました。

モノレポでは、ルートの package.json で依存関係を一元管理できるため、このような不整合を防げます。

以下は、モノレポとマルチレポの比較です。

項目マルチレポモノレポ
型の共有npm パッケージ公開が必要直接参照可能
依存関係各リポジトリで個別管理ルートで一元管理
ビルド各リポジトリで個別実行依存順序で自動制御
CI/CD各リポジトリで設定差分検知で高速化可能
リファクタリング影響範囲の把握が困難IDE で一括変更可能

つまずきポイント

  • モノレポは「すべてを 1 つにまとめる」ことが目的ではありません。関連するプロジェクト群を効率的に管理するための手法です。
  • 無関係なプロジェクトまで同じリポジトリに入れると、かえって管理が複雑になります。

課題:運用で破綻するモノレポの典型例

Yarn Workspaces だけでは不十分な理由

この章では、Workspaces のみでセットアップした場合に起きる実際の問題を理解できます。

Yarn Workspaces は、モノレポの依存関係を解決する仕組みです。packages/web が packages/shared に依存する場合、以下のように package.json に記述します。

json{
  "name": "@myapp/web",
  "dependencies": {
    "@myapp/shared": "workspace:*"
  }
}

しかし、Workspaces だけではビルド順序が保証されません。実際の検証で、packages/web をビルドする際に packages/shared がまだビルドされておらず、型定義ファイル(.d.ts)が見つからないエラーが発生しました。

bash# エラー例
error TS2307: Cannot find module '@myapp/shared' or its corresponding type declarations.

Workspaces は依存関係のインストールを管理しますが、ビルド順序は管理しません。この問題を解決するには、TypeScript の Project References が必要です。

初回ビルドは通るが 2 回目で壊れる問題

業務で遭遇した典型的な問題として、「初回ビルドは成功するが、2 回目以降で型エラーが出る」というケースがありました。

原因は、tsc が生成する .tsbuildinfo ファイル(増分ビルド用のキャッシュ)と、実際の依存関係が不整合を起こしていたためです。composite: true を設定せずに Project References を使うと、この現象が発生します。

json// ❌ 悪い例:composite が抜けている
{
  "compilerOptions": {
    "outDir": "./dist"
  },
  "references": [{ "path": "../shared" }]
}
json// ⭕ 正しい例
{
  "compilerOptions": {
    "composite": true,
    "outDir": "./dist"
  },
  "references": [{ "path": "../shared" }]
}

CI で毎回全パッケージがビルドされる無駄

CI/CD で「変更したのは 1 ファイルだけなのに、全パッケージが再ビルドされて 10 分かかる」という問題も頻発します。

これは、差分検知の仕組みがないためです。tsc --build には増分ビルド機能がありますが、CI では毎回クリーンな環境で実行されるため、キャッシュが効きません。

実務では、Turborepo や Nx などのビルドツールを使い、Git のコミット履歴から変更されたパッケージを検知して、必要な部分だけをビルドする仕組みが必要です。

以下は、差分ビルドの有無による CI 時間の比較です。

mermaidflowchart LR
  commit["コミット"] --> detect["変更検知"]
  detect --> cache["キャッシュ判定"]
  cache --> build1["必要なパッケージのみビルド"]
  cache --> skip["変更なしのパッケージはスキップ"]
  build1 --> fast["高速化"]
  skip --> fast

差分ビルドがない場合、毎回すべてのパッケージをビルドするため、CI 時間が 5〜10 分かかります。差分ビルドを導入すると、変更されたパッケージのみをビルドするため、1〜2 分に短縮できます。

つまずきポイント

  • tsc --build --incremental は、ローカルでは有効ですが、CI では環境が毎回リセットされるため効果が薄いです。
  • CI でキャッシュを活用するには、Turborepo のリモートキャッシュや GitHub Actions の cache アクションが必要です。

解決策と判断:Project References を中心とした設計

Project References とは何か

この章では、TypeScript の Project References を使った依存関係の明示と、ビルド順序の制御方法を理解できます。

Project References は、TypeScript 3.0 で導入された、複数の tsconfig.json を関連付ける機能です。パッケージ間の依存関係を明示し、tsc --build で正しい順序でビルドできます。

json// packages/web/tsconfig.json
{
  "compilerOptions": {
    "composite": true,
    "outDir": "./dist"
  },
  "references": [{ "path": "../shared" }]
}

この設定により、tsc --build は以下の順序で実行されます。

  1. packages/shared をビルド
  2. shared のビルド完了後、packages/web をビルド

composite: true の役割と必須性

composite: true は、Project References を使う際に必須のオプションです。これを設定すると、以下が有効になります。

  • 宣言ファイル(.d.ts)の自動生成
  • .tsbuildinfo ファイルによる増分ビルド
  • rootDir の自動推定

業務では、composite: true を設定し忘れて「ビルドは通るが型が解決されない」というエラーが頻発しました。

json{
  "compilerOptions": {
    "composite": true,
    "declaration": true,
    "declarationMap": true,
    "outDir": "./dist",
    "rootDir": "./src"
  }
}

ルート tsconfig.json の設計判断

ルートの tsconfig.json は、全パッケージ共通の設定を定義します。実務では、以下の方針で設計しました。

  • 共通設定のみをルートに配置:target、module、strict など
  • パッケージ固有の設定は各 tsconfig.json に委譲:jsx、lib など
  • references でパッケージを列挙:ビルド順序を制御
json// tsconfig.json(ルート)
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "skipLibCheck": true,
    "esModuleInterop": true
  },
  "files": [],
  "references": [
    { "path": "./packages/shared" },
    { "path": "./packages/api" },
    { "path": "./packages/web" }
  ]
}

files: [] の記述は重要です。これにより、ルートの tsconfig.json はビルド対象を持たず、references のみを管理する役割に限定されます。

以下は、tsconfig.json の階層構造を示した図です。

mermaidflowchart TD
  root["ルート tsconfig.json<br/>共通設定のみ"]
  shared["packages/shared/tsconfig.json<br/>composite: true"]
  api["packages/api/tsconfig.json<br/>composite: true"]
  web["packages/web/tsconfig.json<br/>composite: true"]

  root --> shared
  root --> api
  root --> web
  api --> shared
  web --> shared

採用しなかった設計とその理由

検証中、以下の設計も試しましたが、採用しませんでした。

パスマッピング(paths)のみで解決する案

json{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@myapp/shared": ["packages/shared/src"]
    }
  }
}

この方法は、TypeScript の型解決には有効ですが、ビルド順序が保証されません。実際の検証で、packages/shared のビルド前に packages/web がビルドされ、型定義ファイルが見つからないエラーが発生しました。

paths は型解決のためのショートカットであり、依存関係の管理には Project References が必要です。

Lerna によるパッケージ管理

Lerna は、モノレポのパッケージ管理ツールですが、2022 年以降メンテナンスが停滞しています。Yarn Workspaces + Turborepo の組み合わせの方が、現在の TypeScript プロジェクトには適しています。

つまずきポイント

  • paths と Project References は役割が異なります。paths は型解決、references はビルド順序の制御です。
  • 両方を併用することも可能ですが、references だけで十分なケースが多いです。

具体例:実際に動作するセットアップ手順

ディレクトリ構造の設計

この章では、実務で採用した具体的なディレクトリ構造と、各ファイルの役割を理解できます。

以下は、実際に検証した構成です。

gomyapp/
├── packages/
│   ├── shared/
│   │   ├── src/
│   │   │   ├── types.ts
│   │   │   └── index.ts
│   │   ├── package.json
│   │   └── tsconfig.json
│   ├── api/
│   │   ├── src/
│   │   │   ├── server.ts
│   │   │   └── routes/
│   │   ├── package.json
│   │   └── tsconfig.json
│   └── web/
│       ├── src/
│       │   ├── App.tsx
│       │   └── components/
│       ├── package.json
│       └── tsconfig.json
├── package.json
├── tsconfig.json
└── turbo.json

ルート package.json の設定

動作確認済みの設定です。

json{
  "name": "myapp",
  "private": true,
  "workspaces": ["packages/*"],
  "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev --parallel",
    "typecheck": "tsc --build",
    "clean": "turbo run clean && rm -rf node_modules/.cache"
  },
  "devDependencies": {
    "typescript": "5.9.3",
    "turbo": "2.3.3"
  },
  "packageManager": "yarn@4.12.0"
}

packageManager フィールドは、Corepack による Yarn バージョンの固定に使います。これにより、チーム全員が同じバージョンの Yarn を使うことが保証されます。

共通パッケージ(shared)の設定

packages/shared は、他のパッケージから参照される型定義とユーティリティを含みます。

json// packages/shared/package.json
{
  "name": "@myapp/shared",
  "version": "0.1.0",
  "main": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "scripts": {
    "build": "tsc",
    "clean": "rm -rf dist"
  },
  "devDependencies": {
    "typescript": "5.9.3"
  }
}
json// packages/shared/tsconfig.json
{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "composite": true,
    "declaration": true,
    "declarationMap": true,
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src/**/*"]
}

extends により、ルートの tsconfig.json から共通設定を継承します。composite、declaration、declarationMap は、Project References に必須の設定です。

API パッケージの設定

packages/api は、Express などのバックエンドフレームワークを使う想定です。

json// packages/api/package.json
{
  "name": "@myapp/api",
  "version": "0.1.0",
  "main": "./dist/server.js",
  "scripts": {
    "build": "tsc",
    "dev": "tsx watch src/server.ts",
    "clean": "rm -rf dist"
  },
  "dependencies": {
    "@myapp/shared": "workspace:*",
    "express": "^4.21.2"
  },
  "devDependencies": {
    "@types/express": "^5.0.0",
    "tsx": "^4.19.2",
    "typescript": "5.9.3"
  }
}

workspace:* により、ワークスペース内の最新バージョンを参照します。

json// packages/api/tsconfig.json
{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "composite": true,
    "declaration": true,
    "declarationMap": true,
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src/**/*"],
  "references": [{ "path": "../shared" }]
}

references で shared への依存を明示します。これにより、tsc --build は shared → api の順にビルドします。

Web パッケージの設定

packages/web は、React や Next.js などのフロントエンドフレームワークを使う想定です。

json// packages/web/package.json
{
  "name": "@myapp/web",
  "version": "0.1.0",
  "scripts": {
    "build": "tsc && vite build",
    "dev": "vite",
    "clean": "rm -rf dist"
  },
  "dependencies": {
    "@myapp/shared": "workspace:*",
    "react": "^18.3.1",
    "react-dom": "^18.3.1"
  },
  "devDependencies": {
    "@types/react": "^18.3.18",
    "@types/react-dom": "^18.3.5",
    "@vitejs/plugin-react": "^4.3.4",
    "typescript": "5.9.3",
    "vite": "^6.0.7"
  }
}
json// packages/web/tsconfig.json
{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "composite": true,
    "declaration": true,
    "declarationMap": true,
    "jsx": "react-jsx",
    "lib": ["ES2022", "DOM"],
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src/**/*"],
  "references": [{ "path": "../shared" }]
}

jsx: "react-jsx" は、React 17 以降の新しい JSX 変換に対応します。

Turborepo によるビルドパイプライン設定

Turborepo は、モノレポのタスクランナーです。依存関係を解析し、並列ビルドとキャッシュを提供します。

json// turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    },
    "typecheck": {
      "dependsOn": ["^build"]
    },
    "clean": {
      "cache": false
    }
  }
}

dependsOn: ["^build"] により、依存パッケージのビルドが先に実行されます。outputs はキャッシュ対象のディレクトリです。

以下は、Turborepo のビルドフローを示した図です。

mermaidflowchart TD
  start["turbo run build"] --> detect["依存関係の解析"]
  detect --> shared["packages/shared のビルド"]
  shared --> parallel["並列実行"]
  parallel --> api["packages/api のビルド"]
  parallel --> web["packages/web のビルド"]
  api --> cache1["キャッシュに保存"]
  web --> cache2["キャッシュに保存"]

実際のビルドコマンドと動作確認

動作確認済みのコマンドです。

bash# 初回セットアップ
corepack enable
yarn install

# 型チェック
yarn typecheck

# 全パッケージのビルド
yarn build

# 開発モード(並列実行)
yarn dev

初回ビルドの出力例です。

bash$ yarn build
• Packages in scope: @myapp/api, @myapp/shared, @myapp/web
• Running build in 3 packages
@myapp/shared:build: cache miss, executing 1a2b3c4d
@myapp/shared:build: compiled successfully
@myapp/api:build: cache miss, executing 5e6f7g8h
@myapp/api:build: compiled successfully
@myapp/web:build: cache miss, executing 9i0j1k2l
@myapp/web:build: compiled successfully

 Tasks:    3 successful, 3 total
Cached:    0 cached, 3 total
  Time:    4.2s

2 回目以降はキャッシュが効きます。

bash$ yarn build
• Packages in scope: @myapp/api, @myapp/shared, @myapp/web
• Running build in 3 packages
@myapp/shared:build: cache hit, replaying output 1a2b3c4d
@myapp/api:build: cache hit, replaying output 5e6f7g8h
@myapp/web:build: cache hit, replaying output 9i0j1k2l

 Tasks:    3 successful, 3 total
Cached:    3 cached, 3 total
  Time:    0.3s >>> FULL TURBO

CI/CD での差分ビルド設定

GitHub Actions での設定例です。

yaml# .github/workflows/ci.yml
name: CI

on:
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: actions/setup-node@v4
        with:
          node-version: 22.21.0

      - run: corepack enable

      - name: キャッシュの復元
        uses: actions/cache@v4
        with:
          path: |
            node_modules
            .turbo
          key: ${{ runner.os }}-${{ hashFiles('yarn.lock') }}

      - run: yarn install --immutable

      - name: 変更されたパッケージのみビルド
        run: yarn turbo run build --filter="...[origin/main]"

      - name: 型チェック
        run: yarn typecheck

--filter="...[origin/main]" により、main ブランチから変更されたパッケージとその依存パッケージのみがビルドされます。

つまずきポイント

  • turbo.json の dependsOn: ["^build"] は、依存パッケージのビルドを意味します。dependsOn: ["build"] は、同じパッケージの build タスクに依存する意味になるため、誤りです。
  • GitHub Actions で fetch-depth: 0 を設定しないと、Git の履歴が浅くなり、差分検知が正しく動作しません。

まとめ:運用で破綻しないモノレポ管理の判断基準

TypeScript モノレポのセットアップでは、Yarn Workspaces だけでなく、Project References による依存関係の明示が必須です。composite: true を設定し、tsconfig.json の階層化を適切に行うことで、ビルド順序が保証され、型安全性が維持されます。

CI/CD では、Turborepo などのツールを使い、変更されたパッケージのみをビルドする仕組みが重要です。キャッシュを活用することで、ビルド時間を数分から数十秒に短縮できます。

ただし、モノレポはすべてのプロジェクトに適しているわけではありません。関連性の低いプロジェクトまで同じリポジトリに入れると、かえって管理コストが増大します。以下の条件に当てはまる場合に、モノレポが有効です。

  • フロントエンドとバックエンドで型定義を共有したい
  • 複数のプロジェクトで同じライブラリバージョンを使いたい
  • リファクタリングの影響範囲を把握しやすくしたい
  • CI/CD を統一して管理したい

逆に、以下の場合はマルチレポの方が適しています。

  • プロジェクト間に依存関係がない
  • チームが完全に分離されている
  • リリースサイクルが大きく異なる

実務では、段階的に導入することをおすすめします。まず共通ライブラリだけをモノレポ化し、運用が安定してから他のパッケージを追加する方法が、失敗が少ないです。

関連リンク

著書

とあるクリエイター

フロントエンドエンジニア Next.js / React / TypeScript / Node.js / Docker / AI Coding

;