# 機能設計書 39-Staged Rendering

## 概要

本ドキュメントは、Next.jsにおけるStaged Rendering機能の設計を記述する。Staged Renderingは段階的レンダリングによるパフォーマンス最適化機能であり、レンダリングプロセスをStatic、Runtime、Dynamicの3段階に分割して制御する。PPR（Partial Prerendering）の内部実装として機能する。

### 本機能の処理概要

**業務上の目的・背景**：PPRの実現には、レンダリングプロセスを段階的に制御する仕組みが必要である。Staged Renderingは、静的コンテンツの生成（Staticステージ）、ランタイムプリフェッチ対応（Runtimeステージ）、動的コンテンツの生成（Dynamicステージ）を明確に分離し、各段階の完了を待機・通知する仕組みを提供する。

**機能の利用シーン**：PPRが有効なApp Routerページのレンダリング時に内部的に使用される。開発者が直接操作することはないが、Suspenseバウンダリの配置により間接的に各ステージの動作に影響する。

**主要な処理内容**：
1. `StagedRenderingController`によるレンダリングステージの状態管理
2. RenderStage列挙型（Before、Static、Runtime、Dynamic、Abandoned）による段階制御
3. Promiseベースのステージ遷移通知メカニズム
4. AbortSignalによるレンダリングキャンセルとabandon処理
5. Sync IO発生時のステージ中断とリトライ機構

**関連システム・外部連携**：PPR（No.38）、Resume Data Cache（No.40）、React Suspense。

**権限による制御**：権限による制御は存在しない。内部実装として自動的に動作する。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | Staged Renderingはレンダリングインフラの内部機構である |

## 機能種別

レンダリング制御 / 状態管理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| abortSignal | AbortSignal \| null | No | レンダリング中断用シグナル | - |
| hasRuntimePrefetch | boolean | Yes | ランタイムプリフェッチが有効かどうか | - |

### 入力データソース

- レンダリングパイプラインからの制御シグナル
- ルートセグメント設定からのPPR/プリフェッチ設定

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| currentStage | RenderStage | 現在のレンダリングステージ |
| staticInterruptReason | Error \| null | Staticステージの中断理由 |
| runtimeInterruptReason | Error \| null | Runtimeステージの中断理由 |
| staticStageEndTime | number | Staticステージの終了時刻 |
| runtimeStageEndTime | number | Runtimeステージの終了時刻 |

### 出力先

- レンダリングパイプラインの内部状態として使用

## 処理フロー

### 処理シーケンス

```
1. StagedRenderingControllerの初期化
   └─ currentStage = Before
   └─ AbortSignalのリスナー設定
2. Staticステージへの遷移
   └─ advanceStage(Static)
   └─ 静的コンテンツのレンダリング実行
3. Runtimeステージへの遷移（キャッシュミスまたはタイムアウト時）
   └─ advanceStage(Runtime)
   └─ staticStageEndTimeの記録
   └─ runtimeStageListenersの通知
4. Dynamicステージへの遷移
   └─ advanceStage(Dynamic)
   └─ runtimeStageEndTimeの記録
   └─ dynamicStageListenersの通知
5. Abandonedステージ（中断時）
   └─ キャッシュミスやSync IO発生時にレンダリングを放棄
   └─ RuntimeステージのPromiseのみを解決（Dynamicは保留）
```

### フローチャート

```mermaid
flowchart TD
    A[Before] --> B[Static]
    B --> C{中断発生?}
    C -->|Sync IO| D{abandonable?}
    D -->|Yes| E[Abandoned]
    D -->|No| F[Dynamic]
    C -->|キャッシュミス| D
    C -->|No| G[Runtime]
    G --> H{Sync IO?}
    H -->|Yes + hasRuntimePrefetch| I[Dynamic]
    H -->|No| J[正常完了 → Dynamic]
    E --> K[リトライレンダリング]
    F --> L[完了]
    I --> L
    J --> L
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-39-01 | ステージ順序 | Before → Static → Runtime → Dynamicの順に遷移する。逆方向の遷移は不可 | 常時 |
| BR-39-02 | Abandoned遷移 | 初回レンダリング（mayAbandon=true）でのみAbandonedに遷移可能 | abortSignalが設定されている場合 |
| BR-39-03 | Sync IO中断 | canSyncInterrupt()がtrueの場合のみSync IOによるステージ中断が可能 | Staticステージ中（Before除く） |
| BR-39-04 | Runtimeプリフェッチ境界 | hasRuntimePrefetchの値によりSync IO中断の境界ステージが変わる | ランタイムプリフェッチ設定 |
| BR-39-05 | delayUntilStage | 非同期処理を指定ステージまで遅延させるPromiseを生成する | データ取得の段階制御 |

### 計算ロジック

Sync IO中断の境界判定:
```
boundaryStage = hasRuntimePrefetch ? RenderStage.Dynamic : RenderStage.Runtime
canSyncInterrupt = currentStage > Before && currentStage < boundaryStage
```

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

該当なし。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| InvariantError | 内部エラー | 無効なRenderStageが指定された場合 | フレームワーク側のバグとして報告 |
| InvariantError | 内部エラー | abandonRenderがmayAbandon=falseで呼ばれた場合 | 内部ロジックエラーとして処理 |
| AbortError | 中断 | AbortSignalによるレンダリングキャンセル | Promiseのrejectで伝搬 |

### リトライ仕様

Abandoned状態からの自動リトライがサポートされている。初回レンダリングがabandonされた場合、新しいStagedRenderingControllerで再レンダリングが実行される。

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

該当なし。

## パフォーマンス要件

- `performance.now() + performance.timeOrigin`による高精度タイムスタンプでステージ遷移時刻を記録
- Promiseベースの非同期通知により、不要なポーリングを回避
- `ignoreReject`によるunhandled rejection警告の抑制

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

- 特になし。内部機構であり外部からのアクセスはない。

## 備考

- Staged Renderingは実験的機能であり、PPRの内部実装として位置づけられている
- React DevtoolsとのIO原因表示のため、Promise.displayNameを設定している
- `createPromiseWithResolvers`ユーティリティを使用してPromise制御を行う

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | staged-rendering.ts | `packages/next/src/server/app-render/staged-rendering.ts` | RenderStage列挙型とStagedRenderingControllerの型を確認する |

**読解のコツ**: **4-10行目**で`RenderStage`列挙型（Before=1, Static=2, Runtime=3, Dynamic=4, Abandoned=5）が定義されている。数値の順序がステージの進行順序を表す。

#### Step 2: コントローラの初期化を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | staged-rendering.ts | `packages/next/src/server/app-render/staged-rendering.ts` | constructorの実装を確認する |

**主要処理フロー**:
1. **30-56行目**: コンストラクタ - abortSignalのリスナー設定。中断時にRuntimeおよびDynamicのPromiseをrejectする
2. **25-26行目**: runtimeStagePromiseとdynamicStagePromise - createPromiseWithResolversで生成された制御可能なPromise

#### Step 3: ステージ遷移を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | staged-rendering.ts | `packages/next/src/server/app-render/staged-rendering.ts` | advanceStageメソッドを確認する |

**主要処理フロー**:
- **179-200行目**: `advanceStage` - ステージ遷移。後方遷移は無視。RuntimeとDynamic到達時にそれぞれ終了時刻を記録し、リスナーを通知

#### Step 4: 中断メカニズムを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | staged-rendering.ts | `packages/next/src/server/app-render/staged-rendering.ts` | syncInterruptCurrentStageWithReasonとabandonRenderImplを確認する |

**主要処理フロー**:
- **83-120行目**: `syncInterruptCurrentStageWithReason` - Sync IO発生時の中断処理。mayAbandonならabandon、そうでなければDynamicへ遷移
- **148-177行目**: `abandonRenderImpl` - レンダリング放棄。Staticなら→Abandoned+Runtime解決、Runtimeならそのまま→Abandoned

#### Step 5: 待機メカニズムを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | staged-rendering.ts | `packages/next/src/server/app-render/staged-rendering.ts` | delayUntilStageメソッドを確認する |

**主要処理フロー**:
- **241-261行目**: `delayUntilStage` - 指定ステージまでPromiseで待機。React DevtoolsのIO原因表示のためdisplayNameを設定
- **269-287行目**: `makeDevtoolsIOPromiseFromIOTrigger` - DevtoolsフレンドリーなPromise生成

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

```
PPRレンダリング
    │
    ├─ new StagedRenderingController(abortSignal, hasRuntimePrefetch)
    │
    ├─ advanceStage(Static)
    │      └─ Staticレンダリング開始
    │
    ├─ delayUntilStage(Runtime, displayName, value)
    │      └─ IO操作の遅延実行
    │
    ├─ advanceStage(Runtime)
    │      └─ resolveRuntimeStage()
    │             ├─ runtimeStageListeners通知
    │             └─ runtimeStagePromise.resolve()
    │
    ├─ advanceStage(Dynamic)
    │      └─ resolveDynamicStage()
    │             ├─ dynamicStageListeners通知
    │             └─ dynamicStagePromise.resolve()
    │
    └─ [異常系] abandonRender()
           └─ Abandoned状態へ遷移
           └─ リトライレンダリングの準備
```

### データフロー図

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

abortSignal ──────▶ StagedRenderingController
hasRuntimePrefetch ▶       │
                     Before → Static → Runtime → Dynamic
                           │         │          │
                     [中断] → Abandoned
                           │
                     staticInterruptReason ────▶ エラー情報
                     runtimeInterruptReason ───▶ エラー情報
                     staticStageEndTime ───────▶ タイムスタンプ
                     runtimeStageEndTime ──────▶ タイムスタンプ
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| staged-rendering.ts | `packages/next/src/server/app-render/staged-rendering.ts` | ソース | StagedRenderingControllerの実装 |
| ppr.ts | `packages/next/src/server/lib/experimental/ppr.ts` | ソース | PPR設定チェック |
| promise-with-resolvers.ts | `packages/next/src/shared/lib/promise-with-resolvers.ts` | ソース | createPromiseWithResolversユーティリティ |
| invariant-error.ts | `packages/next/src/shared/lib/invariant-error.ts` | ソース | InvariantErrorクラス |
