# 機能設計書 76-draftMode()

## 概要

本ドキュメントは、Next.jsの`draftMode()`関数の設計について記述する。`draftMode()`はドラフトモード（下書きプレビュー）の有効/無効を制御するための非同期API関数であり、ヘッドレスCMSとの連携において未公開コンテンツのプレビューを実現する。

### 本機能の処理概要

`draftMode()`関数は、ドラフトモードの状態を管理するDraftModeオブジェクトへのアクセスを提供する。Promiseを返し、awaitすることで`isEnabled`プロパティの読み取り、`enable()`/`disable()`メソッドによるドラフトモードの切り替えが可能になる。ドラフトモードが有効な場合、静的生成されたページの代わりにリクエスト時のサーバーレンダリングが行われ、CMSの下書きコンテンツを表示できる。

**業務上の目的・背景**：ヘッドレスCMS（Contentful, Sanity, Strapi等）を使用する場合、編集者は公開前のコンテンツをプレビューする必要がある。ドラフトモードは、特定のCookieを設定することでISR/SSGページを動的レンダリングに切り替え、CMSのプレビューAPIと連携してドラフトコンテンツを表示可能にする。

**機能の利用シーン**：CMSプレビュー機能の実装、Route Handlerでのドラフトモード有効化、Server Componentsでのドラフトモード状態チェック、プレビューページの動的レンダリング。

**主要な処理内容**：
1. DraftModeProviderを通じたドラフトモード状態の管理
2. `__prerender_bypass` Cookie（COOKIE_NAME_PRERENDER_BYPASS）によるドラフトモード判定
3. `enable()` - ドラフトモード有効化Cookie設定
4. `disable()` - ドラフトモード無効化Cookie削除
5. `isEnabled` - 現在のドラフトモード状態の読み取り

**関連システム・外部連携**：ヘッドレスCMS（プレビューAPI）、Cookie管理（ResponseCookies）、ISR/SSG（プリレンダリングバイパス）

**権限による制御**：`enable()`/`disable()`はServer Actions、Route Handlers内でのみ呼び出し可能。静的生成コンテキストやcache内では呼び出し不可。`isEnabled`の読み取りはServer Components含む広いコンテキストで可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | draftMode()は画面に直接関連しない。CMSプレビュー機能のバックエンド制御に使用される |

## 機能種別

状態管理 / Cookie制御 / Dynamic API

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| - | - | - | draftMode()は引数を取らない | - |

### 入力データソース

- AsyncLocalStorage（workAsyncStorage/workUnitAsyncStorage）
- `__prerender_bypass` Cookie値
- ApiPreviewProps（previewModeId）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| draftMode | Promise\<DraftMode\> | isEnabled, enable(), disable()を持つDraftModeオブジェクト |

### 出力先

- `isEnabled`: Server Componentsでの条件分岐に使用
- `enable()`: `__prerender_bypass` Cookieの設定（Set-Cookieヘッダー経由）
- `disable()`: `__prerender_bypass` Cookieの削除（Set-Cookieヘッダー経由）

## 処理フロー

### 処理シーケンス

```
1. workAsyncStorage/workUnitAsyncStorageからコンテキスト取得
2. WorkUnitStore種別による分岐
   └─ prerender-runtime: delayUntilRuntimeStage
   └─ request: DraftModeProvider付きDraftMode生成
   └─ cache/private-cache/unstable-cache: 外側のRequestStoreのdraftModeProviderを取得
   └─ prerender系: 空DraftMode生成（isEnabled=false）
3. DraftModeProvider初期化（request時）
   └─ __prerender_bypass Cookieの値とpreviewModeIdの一致を検証
   └─ On-Demand Revalidateリクエストの場合はドラフトモード無効
4. enable()呼び出し時
   └─ trackDynamicDraftMode()で動的レンダリング追跡
   └─ mutableCookiesに__prerender_bypass Cookie設定
5. disable()呼び出し時
   └─ trackDynamicDraftMode()で動的レンダリング追跡
   └─ mutableCookiesの__prerender_bypass Cookieを期限切れで削除
```

### フローチャート

```mermaid
flowchart TD
    A[draftMode 呼び出し] --> B{WorkStore/WorkUnitStore存在?}
    B -->|No| Z[throwForMissingRequestStore]
    B -->|Yes| C{WorkUnitStore種別}
    C -->|prerender-runtime| D[delayUntilRuntimeStage]
    C -->|request| E[DraftModeProvider付きDraftMode生成]
    C -->|cache/private-cache/unstable-cache| F{外側にdraftModeProvider?}
    F -->|Yes| G[外側のProvider付きDraftMode]
    F -->|No| H[空DraftMode]
    C -->|prerender/prerender-ppr/prerender-legacy| H
    D --> I[Promise DraftMode 返却]
    E --> I
    G --> I
    H --> I
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-76-01 | Cookie検証 | __prerender_bypass Cookieの値がpreviewModeIdと一致する場合にドラフトモード有効 | リクエスト時 |
| BR-76-02 | 開発モード例外 | 開発モードではCookie値が実際のハッシュでもpreviewModeIdが'development-id'でも有効 | NODE_ENV !== 'production' |
| BR-76-03 | On-Demand Revalidate除外 | On-Demand Revalidateリクエスト時はドラフトモード無効 | revalidateリクエスト時 |
| BR-76-04 | enable/disable制限 | enable()/disable()はafter()内、cache内、静的生成コンテキストでは使用不可 | 該当コンテキスト |
| BR-76-05 | Cookie属性 | ドラフトモードCookieはhttpOnly, secure（本番）, sameSite=none（本番）/lax（開発）で設定 | enable()呼び出し時 |
| BR-76-06 | isEnabled読み取り | isEnabledはcache内でも読み取り可能（外側のRequestStoreから取得） | cache/private-cacheコンテキスト |

### 計算ロジック

特になし。

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

### 操作別データベース影響一覧

draftMode()は直接的なデータベース操作を行わない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| Error | コンテキストエラー | draftMode()がリクエストスコープ外で呼ばれた場合 | Server Components/Actions/Route Handlers内で呼ぶ |
| Error | after()内使用エラー | after()内でenable()/disable()を呼んだ場合 | after()外でドラフトモード制御を行う |
| Error | cache内使用エラー | "use cache"内でenable()/disable()を呼んだ場合 | キャッシュ外で制御する |
| StaticGenBailoutError | 静的生成エラー | dynamic="error"のルートでenable()/disable()を呼んだ場合 | dynamic設定を変更する |
| Error | previewModeId欠落 | enable()時にpreviewModeIdが未設定の場合 | Next.js設定を確認する |

### リトライ仕様

該当なし。

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

該当なし。Cookie操作はSet-Cookieヘッダーを通じてクライアントに反映される。

## パフォーマンス要件

- CachedDraftModes WeakMapによるPromiseキャッシュ
- DraftModeProviderの判定はCookieの値比較のみで軽量

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

- previewModeIdは秘密鍵として機能し、この値を知らないとドラフトモードを有効化できない
- httpOnly属性によりJavaScriptからのCookie直接アクセスを防止
- secure属性（本番環境）によりHTTPS以外での送信を防止
- sameSite属性でクロスサイトリクエスト時のCookie送信を制御

## 備考

- ドラフトモードは旧Preview Mode（getPreviewDataベース）の後継機能
- DraftModeProviderクラスはCookieベースの状態管理を内包する
- 開発モードでは同期アクセス警告がProxy経由で出力される

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | draft-mode-provider.ts | `packages/next/src/server/async-storage/draft-mode-provider.ts` | DraftModeProviderクラスの構造。_isEnabled, _previewModeId, _mutableCookiesプロパティとenable()/disable()メソッドを確認 |

**読解のコツ**: DraftModeProviderはCOOKIE_NAME_PRERENDER_BYPASSの値とpreviewModeIdの一致で判定を行う。enable()はCookieを設定、disable()はexpires: new Date(0)でCookieを削除する。

#### Step 2: エントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | draft-mode.ts | `packages/next/src/server/request/draft-mode.ts` | draftMode()関数本体（25-71行目）。WorkUnitStore種別による分岐とDraftMode生成を確認 |

**主要処理フロー**:
1. **27-28行目**: WorkStore/WorkUnitStoreの取得
2. **34-71行目**: WorkUnitStore種別による分岐
3. **41-42行目**: requestコンテキストでDraftModeProvider付きDraftMode生成
4. **46-57行目**: cache/unstable-cache内では外側のdraftModeProviderを探索
5. **61-66行目**: prerender系では空DraftMode（isEnabled=false）

#### Step 3: DraftModeクラスを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | draft-mode.ts | `packages/next/src/server/request/draft-mode.ts` | DraftModeクラス（126-155行目）。_providerがnullの場合の安全な動作、enable()/disable()時のtrackDynamicDraftMode呼び出しを確認 |

#### Step 4: 動的レンダリング追跡を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | draft-mode.ts | `packages/next/src/server/request/draft-mode.ts` | trackDynamicDraftMode()関数（172-248行目）。enable()/disable()時のコンテキスト別エラー処理・追跡ロジックを確認 |

**主要処理フロー**:
- **179-183行目**: after()内でのenable/disable禁止チェック
- **185-189行目**: dynamicShouldErrorチェック
- **192-201行目**: cache内でのenable/disable禁止
- **207-217行目**: prerenderでのabortAndThrowOnSynchronousRequestDataAccess

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

```
draftMode() [draft-mode.ts:25]
    |
    +-- workAsyncStorage.getStore()
    +-- workUnitAsyncStorage.getStore()
    |
    +-- createOrGetCachedDraftMode(provider, workStore) [draft-mode.ts:73]
    |       +-- [dev] createDraftModeWithDevWarnings() [draft-mode.ts:96]
    |       |       +-- Proxy(Promise<DraftMode>)
    |       +-- new DraftMode(provider) [draft-mode.ts:126]
    |
    +-- DraftMode.enable() [draft-mode.ts:141]
    |       +-- trackDynamicDraftMode() [draft-mode.ts:172]
    |       +-- DraftModeProvider.enable() [draft-mode-provider.ts:61]
    |               +-- mutableCookies.set(__prerender_bypass)
    |
    +-- DraftMode.disable() [draft-mode.ts:149]
            +-- trackDynamicDraftMode() [draft-mode.ts:172]
            +-- DraftModeProvider.disable() [draft-mode-provider.ts:80]
                    +-- mutableCookies.set(__prerender_bypass, expires: 0)
```

### データフロー図

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

AsyncLocalStorage -------> draftMode()                 Promise<DraftMode>
  (WorkStore,                 |                              |
   WorkUnitStore)             +-> DraftModeProvider判定       +-- .isEnabled -> boolean
                              |   (__prerender_bypass         +-- .enable()  -> Cookie設定
リクエストCookie                |    Cookie検証)                +-- .disable() -> Cookie削除
  (__prerender_bypass) ----> +                                    |
                              +-> DraftMode生成                    |
previewModeId ------------>  +                                    v
  (ApiPreviewProps)                                         Set-Cookie ヘッダー
                                                            __prerender_bypass
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| draft-mode.ts | `packages/next/src/server/request/draft-mode.ts` | ソース | draftMode()関数・DraftModeクラス本体 |
| draft-mode-provider.ts | `packages/next/src/server/async-storage/draft-mode-provider.ts` | ソース | DraftModeProviderクラス（Cookie管理） |
| headers.ts (api) | `packages/next/src/api/headers.ts` | ソース | draftMode再エクスポート元 |
| api-utils.ts | `packages/next/src/server/api-utils/index.ts` | ソース | COOKIE_NAME_PRERENDER_BYPASS定数、checkIsOnDemandRevalidate関数 |
| dynamic-rendering.ts | `packages/next/src/server/app-render/dynamic-rendering.ts` | ソース | 動的レンダリング制御関数群 |
