# 機能設計書 70-On-Demand Entry Handler

## 概要

本ドキュメントは、Next.jsのOn-Demand Entry Handler機能の設計について記述する。On-Demand Entry Handlerは開発時のオンデマンドコンパイルを実現する仕組みであり、アクセスされたページのみをビルドすることで開発サーバーの起動時間とメモリ使用量を大幅に削減する。

### 本機能の処理概要

On-Demand Entry Handlerは、開発時に全ページを一括コンパイルするのではなく、ユーザーがブラウザでアクセスしたページのみをオンデマンドでコンパイルし、一定時間アクセスがないページのエントリーを自動的に破棄する仕組みである。

**業務上の目的・背景**：大規模なNext.jsアプリケーションでは、数百～数千のページが存在する場合がある。全ページを常にコンパイルすると、メモリ使用量の増大とコンパイル時間の長期化を招く。On-Demand Entry Handlerにより、アクセスされたページのみをコンパイルし、メモリとCPUリソースを効率的に使用する。

**機能の利用シーン**：`next dev`コマンドでの開発中に自動的に動作する。開発者がブラウザでページにアクセスするたびに、そのページのコンパイルがトリガーされる。

**主要な処理内容**：
1. エントリーマップの管理（entriesMap）：ページごとのビルド状態（ADDED/BUILDING/BUILT）を追跡
2. ページファイルの検索（findPagePathData）：appDir/pagesDir/rootDirからページファイルを探索
3. Invalidator：Webpackコンパイラの無効化と再ビルドの制御
4. エントリーの自動破棄（disposeInactiveEntries）：一定時間アクセスがないページのエントリーを破棄
5. コンパイル完了の待機とコールバック（doneCallbacks）

**関連システム・外部連携**：webpack.MultiCompiler（Webpackコンパイラ群）、HotReloader（HMR通知）、DevServer（開発サーバー）。

**権限による制御**：特になし。開発モードでのみ使用される。

## 関連画面

On-Demand Entry Handlerは画面に直接紐づくものではなく、開発時のビルド最適化基盤として機能する。

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | 任意のページ | コンパイル対象 | ページアクセス時にオンデマンドコンパイルをトリガー |

## 機能種別

開発支援 / ビルド最適化 / エントリー管理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| hotReloader | NextJsHotReloaderInterface | Yes | HotReloaderインスタンス | - |
| maxInactiveAge | number | Yes | エントリーの最大非活性時間（ミリ秒） | 正の整数 |
| multiCompiler | webpack.MultiCompiler | Yes | Webpackの複数コンパイラ | - |
| nextConfig | NextConfigComplete | Yes | Next.js設定 | - |
| pagesBufferLength | number | Yes | ページバッファの長さ | 正の整数 |
| pagesDir | string | No | pagesディレクトリパス | 有効なディレクトリパス |
| rootDir | string | Yes | プロジェクトルートディレクトリ | 有効なディレクトリパス |
| appDir | string | No | appディレクトリパス | 有効なディレクトリパス |

### 入力データソース

- ブラウザからのページアクセスリクエスト
- ファイルシステム（ページファイルの存在確認）
- Webpackコンパイラのコンパイル完了イベント

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| entries | Record<string, Entry \| ChildEntry> | エントリーマップ。各ページのビルド状態を管理 |
| doneCallbacksイベント | EventEmitter | コンパイル完了通知 |

### 出力先

Webpackコンパイラへのエントリー追加/削除。HotReloaderへのビルド完了通知。

## 処理フロー

### 処理シーケンス

```
1. onDemandEntryHandler初期化
   └─ Invalidatorの作成
   └─ Webpackコンパイラへのhookの登録（make、done）
2. ページアクセス発生
   └─ ensurePage()がDevBundlerService経由で呼び出される
3. ページファイルの検索（findPagePathData）
   └─ appDir → pagesDir → _error フォールバック
4. エントリーの追加
   └─ entriesMapにエントリーを登録（status: ADDED）
5. Invalidatorによるコンパイル無効化
   └─ compiler.watching.invalidate()でリビルドをトリガー
6. コンパイル実行
   └─ ステータスがBUILDINGに変更
7. コンパイル完了
   └─ ステータスがBUILTに変更
   └─ doneCallbacksでイベント発火
8. 非活性エントリーの破棄
   └─ disposeInactiveEntries()でmaxInactiveAge超過エントリーを破棄
```

### フローチャート

```mermaid
flowchart TD
    A[ページアクセス] --> B[ensurePage呼び出し]
    B --> C[findPagePathData]
    C --> D{ページファイル存在?}
    D -->|No| E[PageNotFoundError]
    D -->|Yes| F[エントリー登録 ADDED]
    F --> G[Invalidator.invalidate]
    G --> H[コンパイル開始 BUILDING]
    H --> I{コンパイル成功?}
    I -->|Yes| J[ステータス BUILT]
    I -->|No| K[エラー通知]
    J --> L[doneCallbacks発火]
    M[定期チェック] --> N[disposeInactiveEntries]
    N --> O{maxInactiveAge超過?}
    O -->|Yes| P[エントリー破棄]
    O -->|No| Q[維持]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-70-01 | エントリーキー形式 | `{compiler type}@{page type}@{page path}`の形式でエントリーを一意に識別する | 常時 |
| BR-70-02 | ビルド状態遷移 | ADDED → BUILDING → BUILT の順で状態遷移する | エントリーのライフサイクル |
| BR-70-03 | ミドルウェア/Instrumentation保護 | ミドルウェアとInstrumentationファイルのエントリーは自動破棄の対象外 | disposeInactiveEntries実行時 |
| BR-70-04 | 最終アクセスページ保護 | 最後にアクセスされたページは非活性判定の対象外 | lastClientAccessPages/lastServerAccessPagesForAppDir |
| BR-70-05 | ページ検索優先順位 | appDir → pagesDir → _error fallback の順でページファイルを検索 | findPagePathData実行時 |
| BR-70-06 | global-not-found優先 | isGlobalNotFoundEnabled時にglobal-not-foundをnot-foundより優先してロード | UNDERSCORE_NOT_FOUND_ROUTE_ENTRY検索時 |
| BR-70-07 | 並行ビルド制御 | ビルド中にinvalidateが呼ばれた場合、rebuildAgainフラグで次回ビルド後に再度invalidateする | Invalidator.invalidate実行時 |
| BR-70-08 | エントリータイプ | Entry（ページエントリー）とChildEntry（子エントリー、レイアウト等）の2種類 | App Routerのレイアウト階層 |

### 計算ロジック

エントリーキーの計算: `getEntryKey(compilerType, pageBundleType, page)` → `${compilerType}@${pageBundleType}@${pageKey}`

非活性判定: `Date.now() - lastActiveTime > maxInactiveAge`

## データベース操作仕様

本機能はデータベース操作を行わない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| PageNotFoundError | 404 | findPagePathDataでページファイルが見つからない場合 | 404エラーを返却 |
| 'Unknown dynamic param type' | ランタイムエラー | 不明なDynamicParamTypeShortが指定された場合 | エラーをthrow |

### リトライ仕様

コンパイル失敗時は、ファイル再保存により自動的にリトライされる。

## トランザクション仕様

該当なし。

## パフォーマンス要件

- アクセスされたページのみをコンパイルし、未アクセスページのビルドコストをゼロにする
- maxInactiveAgeに基づく自動破棄により、メモリ使用量を制御する
- Invalidatorが並行ビルドを制御し、不要なリビルドを回避する
- ChildEntryはparentEntriesで追跡され、不要時に効率的に破棄可能

## セキュリティ考慮事項

- 開発環境でのみ動作するため、特別なセキュリティ考慮は不要

## 備考

- On-Demand Entry Handlerは`packages/next/src/server/dev/on-demand-entry-handler.ts`に定義される
- Webpack版HotReloaderからのみ使用される（Turbopackは独自のオンデマンドコンパイルを持つ）
- MCP（Model Context Protocol）との統合として、handleErrorStateResponseとhandlePageMetadataResponseも含まれる

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

まず、エントリーの型定義とビルド状態を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | on-demand-entry-handler.ts | `packages/next/src/server/dev/on-demand-entry-handler.ts` | Entry型、ChildEntry型、EntryTypes列挙型、ADDED/BUILDING/BUILTシンボルの定義（171-231行目） |

**読解のコツ**: `Entry`はページファイルのエントリー（absolutePagePath、appPathsを持つ）。`ChildEntry`はレイアウト等の子エントリー（parentEntriesで親を追跡）。ビルド状態はSymbol（ADDED/BUILDING/BUILT）で管理される。

#### Step 2: エントリーキーの生成を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | on-demand-entry-handler.ts | `packages/next/src/server/dev/on-demand-entry-handler.ts` | getEntryKey()関数（116-125行目）。エントリーの一意識別子の生成ロジック |

**主要処理フロー**:
- **116-125行目**: `${compilerType}@${pageBundleType}@${pageKey}`形式でキーを生成
- **127-136行目**: getPageBundleType()でページ種別（PAGES/APP/ROOT）を判定

#### Step 3: ページファイル検索を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | on-demand-entry-handler.ts | `packages/next/src/server/dev/on-demand-entry-handler.ts` | findPagePathData()関数（400-538行目）。ページファイルの検索ロジック |

**主要処理フロー**:
- **408行目**: normalizePagePathでパスを正規化
- **411-443行目**: ミドルウェア/Instrumentationファイルの特別処理
- **446-504行目**: appDirでの検索（global-not-found優先ロジック含む）
- **507-528行目**: pagesDirでの検索
- **530-538行目**: _errorページのフォールバック

#### Step 4: Invalidatorを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | on-demand-entry-handler.ts | `packages/next/src/server/dev/on-demand-entry-handler.ts` | Invalidatorクラス（272-326行目）。並行ビルド制御とリビルドキュー管理 |

**主要処理フロー**:
- **278行目**: MultiCompilerを保持
- **286-301行目**: invalidate()メソッド。ビルド中ならrebuildAgainフラグを立てる
- **307-321行目**: doneBuilding()メソッド。rebuildAgainがあれば再度invalidate()

#### Step 5: メインハンドラを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | on-demand-entry-handler.ts | `packages/next/src/server/dev/on-demand-entry-handler.ts` | onDemandEntryHandler()関数（541行目以降）。初期化処理とWebpackフックの登録 |

**主要処理フロー**:
- **560-568行目**: Invalidatorの作成
- **571-577行目**: compiler.hooks.makeへのtap（ビルド開始追跡）
- **604-610行目**: compiler.hooks.doneへのtap（ビルド完了処理）
- **612-643行目**: multiCompiler.hooks.doneへのtap（エントリーステータス更新）

### プログラム呼び出し階層図

```
onDemandEntryHandler() [on-demand-entry-handler.ts:541]
    │
    ├─ Invalidator [272行目]
    │      ├─ invalidate() → compiler.watching.invalidate()
    │      └─ doneBuilding() → rebuildAgain処理
    │
    ├─ findPagePathData() [400行目]
    │      ├─ appDir検索
    │      │      ├─ global-not-found
    │      │      └─ not-found
    │      ├─ pagesDir検索
    │      └─ _error フォールバック
    │
    ├─ getEntryKey() [116行目]
    │      └─ {compiler}@{pageType}@{page}
    │
    ├─ entriesMap [233行目]
    │      └─ エントリー状態管理（ADDED/BUILDING/BUILT）
    │
    └─ disposeInactiveEntries() [328行目]
           └─ maxInactiveAge超過エントリーの破棄
```

### データフロー図

```
[入力]                         [処理]                            [出力]

ページアクセス要求        ──▶  ensurePage()
                                │
                                ▼
ファイルシステム          ──▶  findPagePathData()
  appDir / pagesDir             │
                                ▼
                              エントリー登録
                              (entriesMap)
                                │
                                ▼
Webpack MultiCompiler    ◀──  Invalidator.invalidate()
                                │
                                ▼
                              コンパイル実行
                              (ADDED → BUILDING → BUILT)
                                │
                                ▼
                              doneCallbacks.emit()    ──▶  HotReloader通知
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| on-demand-entry-handler.ts | `packages/next/src/server/dev/on-demand-entry-handler.ts` | ソース | On-Demand Entry Handler本体 |
| hot-reloader-webpack.ts | `packages/next/src/server/dev/hot-reloader-webpack.ts` | ソース | Webpack HotReloader（On-Demand Entry Handlerの呼び出し元） |
| hot-reloader-types.ts | `packages/next/src/server/dev/hot-reloader-types.ts` | ソース | HMRインターフェース定義（ensurePageメソッド） |
| find-page-file.ts | `packages/next/src/server/lib/find-page-file.ts` | ソース | ページファイル検索ユーティリティ |
| entries.ts | `packages/next/src/build/entries.ts` | ソース | エントリーポイント生成（runDependingOnPageType等） |
