Turborepo × pnpm で実現するモノレポ開発基盤

こんにちは。ぐるなびウエディング開発チームの須永です。普段は主にフロントエンド領域の開発を担当しています。

ぐるなびウエディングは、結婚式場検索・結婚式準備情報を提供するサービスです。
現在、オンプレミスで長年稼働してきた PHP システムをクラウドへ移行し、TypeScript で全面的に書き換えるリニューアルプロジェクトを進めています。

本記事では、このプロジェクトで採用している Turborepo と pnpm を活用したモノレポ開発基盤についてご紹介します。

目次

1. モノレポ採用の背景

ぐるなびウエディングではリニューアルにあたり、以下の理由からモノレポ構成を採用しました。

  • 言語統一による開発効率の向上: フロントエンド(Next.js)とバックエンド(NestJS)を TypeScript で統一することで、エンジニアがフルスタックで開発可能に。言語の切り替えコストを削減し、チーム全体の生産性を向上

  • 型定義の共有: GraphQL スキーマから生成した型をフロントエンド・バックエンドで共有し、API の型の不整合を防止。TypeScript の恩恵を最大限に活用

  • コードの再利用性向上: 共通 UI コンポーネント、ユーティリティ関数などを packages ディレクトリで一元管理し、重複を排除

  • 複数サービスの統合管理: 結婚式、二次会、結納・顔合わせという3つのサービスを効率的に管理し、サービス横断での機能追加や改善をスムーズに

  • 一貫した開発体験: 統一されたツールチェーン(ESLint、Prettier、Vitest)、ビルドシステム(Turborepo)、パッケージマネージャー(pnpm)により、どのサービスでも同じ開発体験を実現

本記事では、モノレポの技術的実装とツール活用に焦点を当て、Turborepo と pnpm を使った開発基盤について、実際のコード例や設定ファイルを交えながら解説します。

2. pnpm Catalogs による依存関係の一元管理

pnpm とは

pnpm は、npm/yarn の代替となる高速なパッケージマネージャーです。シンボリックリンクを使った効率的な依存管理により、ディスク容量を節約しつつインストールを高速化します。特にモノレポのワークスペース機能が充実しており、本プロジェクトでも採用しました。

pnpm Catalogs とは

pnpm v9.5 から導入された Catalogs 機能は、モノレポ全体の依存関係を一元管理する仕組みです。従来は各パッケージの package.json で個別にバージョン指定していましたが、Catalogs により pnpm-workspace.yaml で集中管理できるようになりました。

# pnpm-workspace.yaml
catalogs:
  # フロントエンド: React/Next.js、UI、ブラウザ関連
  frontend:
    'next': '^16' # 注: 実際の実装ではパッチバージョンまで指定
    'react': '^19'
    'react-dom': '^19'
    '@urql/next': '^2'

  # バックエンド: NestJS、GraphQL、データベース、サーバー関連
  backend:
    '@nestjs/core': '^11'
    '@nestjs/graphql': '^13'
    'graphql': '^16'
    'kysely': '^0'

  # 共通: TypeScript、ユーティリティ、共通ライブラリ
  common:
    'typescript': '^5'
    'zod': '^4'

  # テスト関連
  test:
    'vitest': '^3.x'
    '@playwright/test': '^1'

各パッケージでは catalog: プレフィックスで参照します。

{
  "name": "@repo/ui",
  "dependencies": {
    "next": "catalog:frontend",
    "react": "catalog:frontend",
    "react-dom": "catalog:frontend"
  },
  "devDependencies": {
    "vitest": "catalog:test"
  }
}

カテゴリ別の依存管理

依存関係をカテゴリ別に分類することで、用途ごとの管理が容易になります。

  • frontend: React/Next.js エコシステム
  • backend: NestJS/GraphQL エコシステム
  • common: 言語共通のユーティリティ
  • infra: AWS CDK 関連
  • build: ビルドツール
  • lint-format: リント・フォーマットツール
  • test: テストフレームワーク
  • docs: ドキュメント生成ツール

バージョンアップデートの効率化

Catalogs により、バージョンアップデートが一箇所で完結します。

# npm-check-updates で pnpm-workspace.yaml を最新バージョンに一括更新
npx npm-check-updates --packageManager pnpm --workspaces --format group --upgrade

また、catalogMode: strict を設定することで、Catalog に定義されていないバージョンの使用を禁止できます。これにより、チーム全体で統一されたバージョン管理を強制でき、 バージョン不一致による予期しないバグを防げます。

# pnpm-workspace.yaml
catalogMode: strict # デフォルトは 'loose'

3. Turborepo によるビルド最適化

Turborepo とは

Turborepo は、Vercel が開発するモノレポ向けの高速ビルドシステムです。本プロジェクトでは、以下の理由から Turborepo を採用しました。

  • インクリメンタルビルド: 変更があったパッケージのみを再ビルドし、開発効率を大幅に向上
  • 強力なキャッシュ機構: ローカル・リモート両方のキャッシュに対応し、CI/CD でもビルド時間を短縮
  • シンプルな設定: turbo.json ひとつで全タスクのパイプラインを定義でき、学習コストが低い
  • pnpm との相性: pnpm のワークスペースと自然に統合され、追加の設定が不要

主な機能

並列実行とキャッシュ

Turborepo は依存関係グラフを解析し、並列実行可能なタスクを自動的に並列化します。また、一度実行したタスクの結果をキャッシュし、変更がない場合は再実行をスキップします。

# 初回実行
pnpm build
# → すべてのパッケージをビルド

# 2回目の実行(変更なし)
pnpm build
# → キャッシュから復元、大幅に高速化

実際のCI環境での効果を測定したところ、Turborepoのキャッシュにより大幅な時間短縮を実現しています。

  • 初回実行(キャッシュなし): 全てのアプリケーション・パッケージにおいて、lint・型チェック・テスト を実行し約20分
  • 一部のアプリケーションのみ変更(キャッシュヒット): 約5分(75%短縮)

変更のないパッケージはキャッシュから復元されるため、開発者は変更箇所のテストに集中でき、PR ごとの CI 実行時間が大幅に削減されました。
ローカル開発環境でも同様の効果が得られており、コード変更後の検証サイクルが大幅に高速化されています。

turbo.json の設計

Turborepo では、buildtestlint といった各種コマンド(タスク)をプロジェクトルートの turbo.json で一元管理します。各タスクの依存関係、キャッシュ対象、並列実行の可否などを定義することで、モノレポ全体の開発フローを最適化できます。

{
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": [".next/**", "dist/**"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    },
    "lint": {},
    "test": {}
  }
}

ポイント:

  • dependsOn: ["^build"]: 依存パッケージのビルドを先に実行
  • outputs: キャッシュ対象のビルド成果物を指定
  • cache: false: dev サーバーなど、キャッシュ不要なタスク
  • persistent: true: 長時間実行されるタスク(開発サーバーなど)

4. 共通パッケージ設計

packages ディレクトリの設計思想

共通パッケージを用途別に分割して管理しています。ここでは主要なパッケージをご紹介します。

@repo/ui: 共通UIコンポーネントライブラリ

すべてのフロントエンドアプリケーションで共有する UI コンポーネントを提供します。
現在、100個近い共通コンポーネントを管理しており、3つのサービス(結婚式・二次会・結納顔合わせ)で再利用されています。

packages/ui/
├── src/
│   ├── components/     # Button, Card, Header など
│   └── styles/         # 共通スタイル
└── .storybook/         # Storybook 設定

各アプリケーションから共通コンポーネントをインポートして使用します。

import { Button, Card } from '@repo/ui/components/Button'

同じUIコンポーネントを3つのサービスで個別実装する必要がなくなり、開発工数の削減とデザインの一貫性を両立しています。

@repo/graphql: GraphQL型定義の共有

GraphQL Code Generator を使用して、GraphQL スキーマから TypeScript の型定義を自動生成し、フロントエンド・バックエンド間で共有しています。これにより、より型安全な開発が可能になります。

# GraphQL スキーマから型を生成
pnpm codegen
// フロントエンドで型安全に使用
import type { PartyVenuesQuery, PartyVenuesQueryVariables } from '@repo/graphql'

const { data } = await client.query<PartyVenuesQuery, PartyVenuesQueryVariables>({
  query: PartyVenuesDocument,
  variables: { limit: 10 },
})
// data.partyVenues は型付けされており、IDE の補完が効く

その他の共通パッケージ

  • @repo/utils: フロントエンド・バックエンド共通のユーティリティ関数
  • @repo/web-utils: Next.js 共通ライブラリ(API クライアント、カスタムフックなど)
  • @repo/backend-core: バックエンド共通ライブラリ(Elasticsearch クライアント、Redis キャッシュなど)
  • @repo/eslint-config / @repo/typescript-config: 各種ツールの共通設定

ワークスペース間の依存関係

ワークスペース間の依存は workspace:* プロトコルで定義します。

{
  "name": "consumer-party",
  "dependencies": {
    "@repo/ui": "workspace:*",
    "@repo/web-utils": "workspace:*",
    "@repo/graphql": "workspace:*"
  }
}

5. Storybook と VRT 基盤

モノレポにおける Storybook の配置

ぐるなびウエディングのモノレポでは各パッケージやアプリケーションに独立した Storybook を配置しています。

packages/ui/.storybook/              # 共通UIコンポーネント用
apps/web/consumer-party/.storybook/  # 二次会サイト固有
apps/web/consumer-kaoawase/.storybook/ # 顔合わせサイト固有
apps/web/consumer-ceremony/.storybook/ # 結婚式サイト固有
  • 共通設定はパッケージ化し再利用(@repo/storybook-config)
  • Turborepo により複数の Storybook を並列ビルド

ビルド成果物は Docusaurus(ドキュメントサイト生成ツール)と統合し、開発者ポータルとして GitHub Pages で公開しています。これにより、チーム全体で UI コンポーネントのカタログを共有でき、開発者だけでなくデザイナーも最新の UI を確認できます。

VRT 基盤のモノレポ統合

VRT(Visual Regression Testing)を vrt/ ディレクトリで一元管理し、すべての Storybook を対象に自動で UI の視覚的な変更を検出しています。

仕組み:

  • storycap で各 Story のスクリーンショットを自動取得
  • reg-suit で親ブランチとの差分を検出・レポート生成
  • PR へ自動通知し、UI 変更の影響を視覚的に確認可能

PR 作成・更新時に GitHub Actions が自動実行し、意図しない UI の副作用を早期に発見できます。

6. E2E テスト基盤

モノレポでの E2E テスト統合

E2E テストを e2e/ ディレクトリで一元管理し、Web・API・Batch すべてのテストを統合的に実施しています。

e2e/
├── playwright.config.ts        # Web 用設定
├── playwright-api.config.ts    # API 用設定
├── playwright-bat.config.ts    # Batch 用設定
├── web/                        # フロントエンドE2E
├── api/                        # バックエンドE2E
└── bat/                        # バッチE2E

Playwright による統一的なテスト環境:

モノレポの利点を活かし、Web・API・Batch すべてのテストに Playwright を採用。統一されたテストフレームワークにより、学習コストを削減しています。

// Web E2E: UI/UX のテスト
test('会場詳細ページが正しく表示される', async ({ page }) => {
  await page.goto('/venue/123')
  expect(await page.title()).toContain('会場名')
})

// API E2E: GraphQL API のテスト
test('会場検索APIが正しいデータを返す', async ({ request }) => {
  const response = await request.post('/graphql', {
    data: { query: venuesQuery, variables: { limit: 10 } },
  })
  expect(response.ok()).toBeTruthy()
})

7. デプロイ戦略と CI/CD

モノレポにおける選択的デプロイ

各サービス(Web3種、API、Batch)ごとに独立した GitHub Actions ワークフローを定義し、手動トリガーでデプロイを実行しています。

デプロイの特徴:

  • 手動デプロイ: workflow_dispatch による明示的なトリガーで、本番環境への影響を最小化
  • 環境選択: staging/prod など、実行時に環境を選択可能
  • 統一的な管理: 単一リポジトリで全サービスのデプロイワークフローを管理し、共通の CI/CD パターンを維持

8. モノレポ運用の実際

モノレポは多くのメリットをもたらしますが、運用には独自の課題や工夫も伴います。ここでは、実際の運用における課題や取り組みについてご紹介します。

月一の依存パッケージ更新

ウエディングチームでは、運用業務として月に一度依存パッケージの更新作業を行っています。
Catalogs による一元管理でバージョン指定自体は効率化されますが、更新の影響範囲が広いため、パッケージ更新作業には相応の時間と慎重さが求められます。

  • 広範囲な影響範囲: 全体で共通的に使われるパッケージの場合、1つの Catalog エントリを更新すると20以上のワークスペース全体に影響するため、各パッケージでの動作確認が必要
  • 依存パッケージの総数: 20以上のワークスペースがそれぞれ依存パッケージを持つため、全体としての依存パッケージ数が多く、更新時に問題が発生するリスクが高まる
  • メジャーバージョンアップ時の複雑さ: 破壊的変更を含むメジャーバージョンアップでは、依存関係を考慮した段階的な更新戦略が求められる

対策:

  • 詳細な更新手順のドキュメント化: Docusaurus で段階的な更新手順を整備し、チーム全体で共有
  • 小さな単位での動作確認: 各カテゴリの更新後に lint, type:check, test, build を実行し、問題を早期発見
  • 月次の定期実行: 更新を習慣化し、一度に大量の更新が発生しないよう運用

継続的な改善とチーム文化

本プロジェクトのモノレポ構成は、最小限の構成でスタートし、開発を進めながら課題を見つけ、チームで相談しながら継続的に改善してきました。

改善の積み重ね:

  • 段階的なパッケージ切り出し: 実装を進める中で重複コード・コンポーネントを段階的に共通パッケージに切り出し
  • CI/CD の最適化: キャッシュ活用やワークフローの改善を重ね、実行時間短縮を図る
  • ドキュメントの充実: Docusaurus で開発ガイドやアーキテクチャ図を整備し、チーム全体の知識を集約

チーム文化の醸成:

  • 朝会・もくもく会: チームメンバーが相談できる場を定期的に設け、課題の早期発見と解決を促進
  • リファクタリングの習慣化: 機能開発と並行して、より良い構造への改善を継続的に実施

このように、チームで対話しながら少しずつ改善を重ねることで現在の開発基盤を構築してきました。

9. まとめ

Turborepo × pnpm で実現したこと

本記事では、Turborepo と pnpm を活用したモノレポ開発基盤の技術的実装について、具体的な設定やコード例を交えてご紹介しました。

特に以下の点が、開発効率の向上に大きく貢献しています。

  • pnpm Catalogs: 依存関係の一元管理によるバージョン更新の効率化
  • Turborepo のキャッシュ: インクリメンタルビルドと並列実行によるビルド時間の大幅な短縮
  • 共通パッケージ設計: GraphQL 型定義や UI コンポーネントの共有による開発効率向上
  • Storybook + VRT: UI コンポーネントの統合カタログ化とビジュアルリグレッション防止
  • E2E テスト基盤: Playwright による統一的なテスト環境の構築
  • 統一的なデプロイ管理: 単一リポジトリで全サービスのデプロイワークフローを管理

モノレポツールの選定や運用方法は、プロジェクトの規模や特性によって最適解が異なります。本記事の実例が、これからモノレポ導入を検討される方の参考になれば幸いです。


フロントエンドエンジニアです。
旅行と海鮮料理が好きです。