Next.js × pnpm/Turborepo 初期構築:ワークスペース・共有パッケージ・CI 最適化

Next.js プロジェクトが大きくなるにつれ、複数のアプリケーション(Web フロント、管理画面、LP など)や共有ライブラリを効率的に管理する必要性が高まります。本記事では、pnpm のワークスペース機能と Turborepo を組み合わせて、モノレポ構成を最初から正しく構築する方法を解説します。
特に以下のテーマに焦点を当てています。
- pnpm ワークスペースによる依存関係の一元管理
- Turborepo によるビルド・テストの高速化
- 共有パッケージ(UI コンポーネント、共通ロジック)の効率的な運用
- CI/CD パイプラインの最適化(キャッシュ戦略・並列実行)
これから Next.js でモノレポを始める方、既存プロジェクトをスケールさせたい方に役立つ実践的な内容となっています。
背景
モノレポが選ばれる理由
従来、複数のプロジェクトを別々のリポジトリで管理するマルチリポ構成が一般的でした。しかし、プロジェクトが増えると以下の課題が顕在化します。
- 共通コンポーネントやユーティリティを各リポジトリにコピー&ペーストする必要がある
- 依存関係のバージョンが統一されず、互換性問題が発生しやすい
- CI/CD パイプラインをリポジトリごとに設定・管理しなければならない
こうした問題を解決するため、複数のプロジェクトを 1 つのリポジトリで管理するモノレポ構成が注目されています。
pnpm と Turborepo の役割
モノレポを実現するツールには、Yarn Workspaces、Lerna、Nx、Turborepo などがありますが、本記事では pnpm + Turborepo の組み合わせを採用します。
以下の図は、pnpm と Turborepo それぞれの役割を示しています。
mermaidflowchart TB
subgraph pnpm ["pnpm Workspace"]
direction TB
pkg["依存関係の一元管理<br/>(シンボリックリンク)"]
install["高速なインストール<br/>(ストレージ最適化)"]
end
subgraph turbo ["Turborepo"]
direction TB
cache["タスクキャッシュ<br/>(リモート/ローカル)"]
parallel["並列実行<br/>(依存関係グラフ)"]
end
pnpm -->|パッケージ管理| turbo
turbo -->|ビルド最適化| result["高速なビルド・CI"]
pnpm は依存関係のインストールと管理を効率化し、Turborepo はビルドやテストなどのタスク実行を高速化します。両者を組み合わせることで、開発速度と CI パフォーマンスが劇的に向上します。
技術選定のポイント
# | ツール | 役割 | 選定理由 |
---|---|---|---|
1 | pnpm | パッケージマネージャー | インストールが高速で、ディスク容量を節約できる |
2 | Turborepo | ビルドツール | キャッシュ機能が強力で、CI 時間を大幅に短縮 |
3 | Next.js | フロントエンドフレームワーク | SSR/SSG に対応し、モノレポとの相性が良い |
課題
モノレポ導入時の典型的な問題
モノレポ構成を採用する際、以下のような課題に直面することがあります。
1. 依存関係の複雑化
複数のパッケージが相互に依存すると、バージョン管理が煩雑になります。特に、共有パッケージを更新した際に、依存する全アプリケーションで動作確認が必要になります。
2. ビルド時間の増大
モノレポ内のパッケージ数が増えると、全体のビルド時間が長くなります。変更のないパッケージまで毎回ビルドされると、開発効率が低下します。
3. CI/CD の非効率性
従来の CI 設定では、コードの一部変更でもリポジトリ全体をビルド・テストする必要があり、CI 実行時間が増大します。
以下の図は、モノレポにおける課題の全体像を示しています。
mermaidflowchart TD
mono["モノレポ"] --> issue1["依存関係の複雑化"]
mono --> issue2["ビルド時間の増大"]
mono --> issue3["CI/CD の非効率性"]
issue1 --> problem1["バージョン管理が煩雑"]
issue2 --> problem2["変更なしでも全ビルド"]
issue3 --> problem3["全体テストで時間増"]
これらの課題を解決するために、pnpm のワークスペース機能と Turborepo のキャッシュ機構を活用します。
解決策
pnpm Workspace による依存関係の一元管理
pnpm のワークスペース機能を使うことで、複数のパッケージを 1 つのリポジトリで効率的に管理できます。
ワークスペース構成のメリット
- 依存関係がルートの
node_modules
に集約され、重複インストールを回避 - シンボリックリンクにより、ローカルパッケージを直接参照可能
- バージョン管理が一元化され、整合性が保たれる
ディレクトリ構成
以下は、推奨するモノレポのディレクトリ構造です。
bashmonorepo/
├── apps/
│ ├── web/ # Next.js メインアプリ
│ └── admin/ # Next.js 管理画面
├── packages/
│ ├── ui/ # 共有 UI コンポーネント
│ ├── utils/ # 共通ユーティリティ
│ └── config/ # 共通設定(ESLint, TypeScript など)
├── package.json
├── pnpm-workspace.yaml
└── turbo.json
以下の図は、pnpm ワークスペースにおけるパッケージ間の依存関係を示しています。
mermaidflowchart LR
web["apps/web"] -->|依存| ui["packages/ui"]
admin["apps/admin"] -->|依存| ui
web -->|依存| utils["packages/utils"]
admin -->|依存| utils
ui -->|依存| utils
apps/web
と apps/admin
は、共有パッケージである packages/ui
と packages/utils
に依存しています。pnpm はこれらをシンボリックリンクで効率的に管理します。
Turborepo によるビルド・テストの高速化
Turborepo は、タスクの依存関係を解析し、キャッシュと並列実行により高速化を実現します。
Turborepo の主要機能
# | 機能 | 説明 |
---|---|---|
1 | タスクキャッシュ | 一度実行したタスクの結果をキャッシュし、再実行を省略 |
2 | 並列実行 | 依存関係のないタスクを並列に実行 |
3 | リモートキャッシュ | CI 環境でキャッシュを共有し、ビルド時間を短縮 |
以下の図は、Turborepo のタスク実行フローを示しています。
mermaidflowchart TB
start["タスク開始"] --> check["キャッシュ確認"]
check -->|キャッシュあり| skip["実行スキップ"]
check -->|キャッシュなし| exec["タスク実行"]
exec --> save["結果をキャッシュ"]
save --> done["完了"]
skip --> done
キャッシュが存在する場合、タスクの実行をスキップし、大幅に時間を短縮できます。
CI/CD パイプラインの最適化
Turborepo のリモートキャッシュ機能を活用することで、CI 環境でも高速なビルドが可能になります。
CI 最適化のポイント
- 変更のあったパッケージのみビルド・テスト
- リモートキャッシュを利用し、過去のビルド結果を再利用
- 並列実行により、複数のタスクを同時に処理
以下の図は、CI パイプラインにおけるキャッシュ戦略を示しています。
mermaidsequenceDiagram
participant CI as CI 環境
participant Cache as リモートキャッシュ
participant Build as ビルドタスク
CI->>Cache: キャッシュ確認
alt キャッシュあり
Cache-->>CI: キャッシュデータ返却
CI->>Build: ビルドスキップ
else キャッシュなし
CI->>Build: ビルド実行
Build-->>CI: ビルド結果
CI->>Cache: 結果をキャッシュ保存
end
リモートキャッシュにより、チーム全体で CI の高速化を共有できます。
具体例
ここでは、pnpm + Turborepo を使った Next.js モノレポの構築手順を、段階的に解説します。
ステップ 1: プロジェクトの初期化
まず、プロジェクトのルートディレクトリを作成し、pnpm を初期化します。
bashmkdir my-monorepo
cd my-monorepo
pnpm init
package.json
が生成されます。次に、pnpm ワークスペースの設定を行います。
ステップ 2: pnpm ワークスペースの設定
ルートディレクトリに pnpm-workspace.yaml
を作成します。
yamlpackages:
- 'apps/*'
- 'packages/*'
このファイルにより、apps/
と packages/
配下のディレクトリがワークスペースとして認識されます。
ステップ 3: Next.js アプリケーションの作成
apps/
配下に Next.js アプリケーションを作成します。
bashmkdir -p apps/web
cd apps/web
pnpm create next-app@latest . --typescript --tailwind --app --src-dir --import-alias "@/*"
同様に、管理画面用のアプリケーションも作成します。
bashmkdir -p apps/admin
cd apps/admin
pnpm create next-app@latest . --typescript --tailwind --app --src-dir --import-alias "@/*"
ステップ 4: 共有パッケージの作成
共有 UI コンポーネント用のパッケージを作成します。
bashmkdir -p packages/ui
cd packages/ui
pnpm init
packages/ui/package.json
を以下のように設定します。
json{
"name": "@my-monorepo/ui",
"version": "0.0.1",
"main": "./src/index.ts",
"types": "./src/index.ts",
"scripts": {
"lint": "eslint .",
"type-check": "tsc --noEmit"
},
"dependencies": {
"react": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.2.0",
"typescript": "^5.0.0"
}
}
name
フィールドには、スコープ付きのパッケージ名(例: @my-monorepo/ui
)を指定します。これにより、他のパッケージから @my-monorepo/ui
として参照できます。
ステップ 5: 共有パッケージの実装
packages/ui/src/index.ts
にシンプルなボタンコンポーネントを作成します。
typescript// packages/ui/src/index.ts
export { Button } from './Button';
typescript// packages/ui/src/Button.tsx
import React from 'react';
interface ButtonProps {
children: React.ReactNode;
onClick?: () => void;
}
/**
* 共通ボタンコンポーネント
* モノレポ内の全アプリで利用可能
*/
export const Button: React.FC<ButtonProps> = ({
children,
onClick,
}) => {
return (
<button
onClick={onClick}
className='px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600'
>
{children}
</button>
);
};
このコンポーネントは、モノレポ内の全アプリケーションで共有されます。
ステップ 6: アプリケーションから共有パッケージを利用
apps/web
から @my-monorepo/ui
を依存関係に追加します。
bashcd apps/web
pnpm add @my-monorepo/ui@workspace:*
workspace:*
を指定することで、ワークスペース内のローカルパッケージを参照します。
次に、apps/web/src/app/page.tsx
でボタンコンポーネントを使用します。
typescript// apps/web/src/app/page.tsx
import { Button } from '@my-monorepo/ui';
/**
* メインページ
* 共有 UI コンポーネントを利用
*/
export default function Home() {
return (
<main className='flex min-h-screen flex-col items-center justify-center'>
<h1 className='text-4xl font-bold mb-8'>
Welcome to Next.js Monorepo
</h1>
<Button onClick={() => alert('Clicked!')}>
Click Me
</Button>
</main>
);
}
これにより、共有パッケージのコンポーネントをアプリケーション内で利用できます。
ステップ 7: Turborepo のセットアップ
ルートディレクトリに戻り、Turborepo をインストールします。
bashcd ../..
pnpm add -Dw turbo
-Dw
オプションは、ワークスペースのルートに devDependency として追加することを意味します。
ステップ 8: Turborepo の設定
ルートディレクトリに turbo.json
を作成し、タスクの設定を行います。
json{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "!.next/cache/**"]
},
"lint": {
"outputs": []
},
"type-check": {
"outputs": []
},
"dev": {
"cache": false,
"persistent": true
}
}
}
この設定により、以下のタスクが最適化されます。
# | タスク | 説明 |
---|---|---|
1 | build | ビルド結果を .next/** にキャッシュ(.next/cache を除く) |
2 | lint | ESLint による静的解析を実行 |
3 | type-check | TypeScript の型チェックを実行 |
4 | dev | 開発サーバーを起動(キャッシュ無効) |
dependsOn
フィールドには、タスクの依存関係を指定します。^build
は「依存パッケージの build
タスクが完了してから実行」を意味します。
ステップ 9: ルートの package.json にスクリプトを追加
ルートの package.json
に Turborepo を使ったスクリプトを追加します。
json{
"name": "my-monorepo",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "turbo run dev",
"build": "turbo run build",
"lint": "turbo run lint",
"type-check": "turbo run type-check"
},
"devDependencies": {
"turbo": "^1.10.0"
}
}
これにより、pnpm dev
でワークスペース全体の開発サーバーが起動し、pnpm build
で全パッケージをビルドできます。
ステップ 10: ビルドとキャッシュの確認
最初のビルドを実行します。
bashpnpm build
初回は全パッケージがビルドされますが、2 回目以降は変更がない限りキャッシュが使われます。
bashpnpm build
2 回目の実行では、以下のようなログが表示され、キャッシュが利用されたことが確認できます。
makefile• Packages in scope: @my-monorepo/ui, web, admin
• Running build in 3 packages
• Remote caching disabled
@my-monorepo/ui:build: cache hit, replaying output
web:build: cache hit, replaying output
admin:build: cache hit, replaying output
Tasks: 3 successful, 3 total
Cached: 3 cached, 3 total
Time: 0.5s >>> FULL TURBO
FULL TURBO
と表示されれば、全タスクがキャッシュから復元されています。
ステップ 11: CI でのリモートキャッシュ設定
Turborepo のリモートキャッシュを利用すると、CI 環境でもキャッシュを共有できます。Vercel が提供する無料のリモートキャッシュを使う場合、以下の手順で設定します。
まず、Turborepo のアカウントにログインします。
bashnpx turbo login
次に、リモートキャッシュをリンクします。
bashnpx turbo link
これにより、.turbo
ディレクトリにトークンが保存され、CI 環境でもキャッシュを利用できます。
ステップ 12: GitHub Actions での CI 設定
.github/workflows/ci.yml
を作成し、CI パイプラインを設定します。
yamlname: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 8
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run lint
run: pnpm lint
- name: Run type-check
run: pnpm type-check
- name: Run build
run: pnpm build
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
TURBO_TOKEN
と TURBO_TEAM
は、GitHub のリポジトリシークレットに登録します。これにより、CI 環境でリモートキャッシュが利用されます。
以下の図は、CI パイプラインの実行フローを示しています。
mermaidflowchart TB
checkout["コードチェックアウト"] --> setup["Node.js & pnpm セットアップ"]
setup --> install["依存関係インストール"]
install --> lint["Lint 実行"]
install --> typecheck["型チェック実行"]
lint --> build["ビルド実行"]
typecheck --> build
build --> cache["リモートキャッシュ保存"]
cache --> finish["CI 完了"]
Lint と型チェックは並列に実行され、その後ビルドが実行されます。
図で理解できる要点
- pnpm ワークスペースにより、依存関係がシンボリックリンクで効率的に管理される
- Turborepo のキャッシュ機構により、変更のないタスクは実行がスキップされる
- CI 環境でリモートキャッシュを利用することで、ビルド時間が大幅に短縮される
まとめ
本記事では、Next.js プロジェクトにおいて pnpm と Turborepo を組み合わせたモノレポ構成を、初期構築から CI 最適化まで段階的に解説しました。
主なポイントは以下の通りです。
- pnpm ワークスペースにより、複数のパッケージを効率的に管理し、依存関係の重複を回避できます
- Turborepo のタスクキャッシュと並列実行により、ビルド・テスト時間が劇的に短縮されます
- 共有パッケージ(UI コンポーネント、ユーティリティ)を作成することで、コードの再利用性が向上します
- リモートキャッシュを CI 環境で活用することで、チーム全体の開発効率が向上します
モノレポ構成は、初期設定こそ手間がかかりますが、長期的にはコード管理やビルド時間の面で大きなメリットがあります。特に、複数のアプリケーションや共通ライブラリを抱えるプロジェクトでは、導入を検討する価値があるでしょう。
今回紹介した構成を基盤として、プロジェクトの成長に合わせて拡張していくことができます。
関連リンク
- article
Next.js × pnpm/Turborepo 初期構築:ワークスペース・共有パッケージ・CI 最適化
- article
Next.js Route Handlers vs API Routes:設計意図・性能・制約のリアル比較
- article
Remix と Next.js/Vite/徹底比較:選ぶべきポイントはここだ!
- article
【実測検証】Remix vs Next.js vs Astro:TTFB/LCP/開発体験を総合比較
- article
Next.js で「Dynamic server usage: cookies/headers」はなぜ起きる?原因と解決手順
- article
Zustand × Next.js の Hydration Mismatch を根絶する:原因別チェックリスト
- article
Homebrew コマンドチートシート 2025:毎日使う 60 コマンド即参照リスト
- article
Node.js クリーンアーキテクチャ実践:アダプタ/ユースケース/エンティティの分離
- article
gpt-oss のモデルルーティング設計:サイズ別・ドメイン別・コスト別の自動切替
- article
【保存版】GPT-5 プロンプト設計チートシート:ロール/制約/出力形式のテンプレ集
- article
Next.js × pnpm/Turborepo 初期構築:ワークスペース・共有パッケージ・CI 最適化
- article
NotebookLM とは?Google 製 AI ノートの仕組みとできることを 3 分で解説
- blog
iPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
- blog
Googleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
- blog
【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
- blog
Googleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
- blog
Pixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
- blog
フロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来