# 機能設計書 44-use cache

## 概要

本ドキュメントは、Next.jsの`"use cache"`ディレクティブの設計を記述する。関数やコンポーネント単位でキャッシュを制御する実験的機能であり、React Server Componentsのストリーミングとキャッシュハンドラを統合した次世代のキャッシュシステムである。

### 本機能の処理概要

**業務上の目的・背景**：従来の`unstable_cache`やfetch拡張によるキャッシュは、データ取得単位のキャッシュに限定されていた。`"use cache"`ディレクティブは、Server ComponentのレンダリングUI自体をRSC（React Server Components）ストリーム形式でキャッシュし、コンポーネント単位・関数単位でのきめ細かいキャッシュ制御を実現する。`"use cache"`（パブリック）、`"use cache: private"`（プライベート）の2種類があり、プライベートキャッシュはリクエスト固有のデータ（Cookie等）にアクセス可能である。

**機能の利用シーン**：Server Componentsのレンダリング結果キャッシュ、ページ/レイアウト単位のキャッシュ、高コストなUI計算のキャッシュ、プライベートキャッシュによるユーザー固有データのキャッシュ。

**主要な処理内容**：
1. `"use cache"`ディレクティブの検出とSWCによるコード変換
2. キャッシュキーの生成（buildId + functionId + args + hmrRefreshHash）
3. Resume Data Cache（RDC）からのキャッシュルックアップ
4. CacheHandler（default/remote）からのキャッシュルックアップ
5. キャッシュミス時のRSCストリーム生成とキャッシュ保存
6. staleエントリのバックグラウンド再検証
7. cacheLife/cacheTagによるキャッシュメタデータの収集と伝播

**関連システム・外部連携**：CacheHandler（default/remote）、Resume Data Cache、React Server Components（react-server-dom-webpack）、SWC（コード変換）

**権限による制御**：`"use cache: private"`はリクエストコンテキスト（Cookie/ヘッダー）にアクセス可能。パブリック`"use cache"`はリクエスト固有データにアクセス不可。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | 本機能は画面に直接関連しない。Server Componentsのレンダリングレイヤーで動作する |

## 機能種別

キャッシュ制御 / レンダリング最適化

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| kind | string | Yes | キャッシュの種類（'default', 'remote', 'private'） | 未知のkindはエラー |
| id | string | Yes | 関数のAction ID | - |
| boundArgsLength | number | Yes | バインドされた引数の数 | - |
| originalFn | Function | Yes | キャッシュ対象の関数 | - |
| args | unknown[] | Yes | 関数の引数 | - |

### 入力データソース

SWCによるコード変換で`cache()`関数呼び出しに変換される。WorkStore/WorkUnitStoreからレンダリングコンテキストを取得。

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| result | unknown | RSCストリームからデシリアライズされた関数の戻り値 |

### 出力先

呼び出し元のServer Component。キャッシュデータはCacheHandler（メモリ/リモート）およびResume Data Cacheに保存。

## 処理フロー

### 処理シーケンス

```
1. cache(kind, id, boundArgsLength, fn, args) 呼び出し
2. WorkStore/WorkUnitStore取得
3. CacheContext（public/private）の決定
4. キャッシュキー生成（buildId + id + args + hmrRefreshHash）
5. encodeReplyでキャッシュキーをシリアライズ
6. Resume Data Cacheからルックアップ
   └─ ヒット → stale/expire/revalidate:0チェック → ストリーム返却
7. CacheHandlerからルックアップ
   └─ ヒット → タグ有効期限チェック → ストリーム返却
8. ミス → generateCacheEntry()でRSCストリーム生成
   └─ クリーンなAsyncLocalStorageコンテキストで実行
   └─ renderToReadableStream/prerenderでRSCストリーム生成
   └─ collectResult()でタグ・revalidate・expire・staleを収集
9. CacheHandler.set()でキャッシュ保存
10. Resume Data Cacheにもエントリを保存
11. createFromReadableStreamでRSCストリームをデシリアライズして返却
```

### フローチャート

```mermaid
flowchart TD
    A[cache関数呼び出し] --> B[CacheContext決定]
    B --> C[キャッシュキー生成]
    C --> D{RDCにエントリ?}
    D -->|Yes| E{stale/expire条件}
    E -->|有効| F[RDCストリーム返却]
    E -->|無効/短命| G{CacheHandlerにエントリ?}
    D -->|No| G
    G -->|Yes| H{タグ有効期限チェック}
    H -->|有効| I[CacheHandlerストリーム返却]
    H -->|無効| J[generateCacheEntry]
    G -->|No| J
    J --> K[RSCストリーム生成]
    K --> L[CacheHandler.set]
    L --> M[RDCに保存]
    M --> N[RSCストリームデシリアライズ]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-44-01 | DYNAMIC_EXPIRE | revalidate:0またはexpire<300秒のエントリは静的プリレンダリング時に動的扱い | prerender時 |
| BR-44-02 | RUNTIME_PREFETCH_DYNAMIC_STALE | stale<30秒のエントリはランタイムプリレンダリング時に動的扱い | prerender-runtime時 |
| BR-44-03 | プライベートキャッシュ制約 | "use cache: private"はpublic "use cache"内やunstable_cache内で使用不可 | ネスト時 |
| BR-44-04 | クリーンコンテキスト | キャッシュ生成はクリーンなAsyncLocalStorageスナップショットで実行 | 常時 |
| BR-44-05 | Draft Modeバイパス | Draft Mode時はキャッシュを保存しない | workStore.isDraftMode |
| BR-44-06 | 最近再検証されたタグ | pendingRevalidatedTagsまたはpreviouslyRevalidatedTagsに含まれるタグを持つエントリは破棄 | Server Action後 |
| BR-44-07 | タイムアウト | プリレンダリング時は50秒のタイムアウトが設定される | prerender/prerender-runtime |

### 計算ロジック

- キャッシュキー: `[buildId, id, args]`（+ hmrRefreshHash in dev）をencodeReplyでシリアライズ
- revalidate/expire/stale: cacheLife()による明示値があればそれを使用、なければ内部fetch/unstable_cacheからの伝播値

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

本機能はRDBを使用しない。CacheHandler（メモリ/リモート）とResume Data Cacheを使用。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | Error | WorkStore外での呼び出し | エラースロー |
| - | Error | 未知のcache handler種別 | エラースロー |
| - | InvariantError | cacheLifeProfilesが未提供 | InvariantError |
| - | UseCacheTimeoutError | プリレンダリング時に50秒タイムアウト | ストリームをエラーで閉じる |
| - | InvariantError | クライアントコンポーネント内での使用 | InvariantError |

### リトライ仕様

staleエントリ返却時にバックグラウンドで再検証を実行。再検証失敗時はstaleデータを返却し続ける。

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

不要。CacheHandlerのset操作はpendingRevalidateWritesに追加され非同期で完了を待つ。

## パフォーマンス要件

- RSCストリームのキャッシュにより、Server Componentの再レンダリングを回避
- stale-while-revalidateパターンでユーザーへの応答を高速化
- プリレンダリング時は50秒タイムアウトで無限待機を防止

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

- キャッシュ生成はクリーンなAsyncLocalStorageコンテキストで実行し、リクエスト固有データの漏洩を防止
- パブリックキャッシュではsearchParamsがキャッシュキーから除外され、searchParamsアクセス時にエラーを発生
- バインドされた引数は暗号化されており、`decryptActionBoundArgs`でデコード
- プライベートキャッシュはResume Data Cacheのみに保存され、CacheHandlerには保存されない

## 備考

- `cacheComponents`設定が必要（`process.env.__NEXT_USE_CACHE`で確認）
- プライベートキャッシュ（`"use cache: private"`）は動的リクエストとランタイムプリフェッチでのみ使用
- ページ/レイアウトのセグメント関数には特別なparams/searchParams処理が適用される
- コンソールログはキャッシュヒット時もリプレイされる（replayConsoleLogs: true）

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | types.ts | `packages/next/src/server/lib/cache-handlers/types.ts` | CacheEntry型（6-40行目）: value, tags, stale, timestamp, expire, revalidateの構造 |
| 1-2 | constants.ts | `packages/next/src/server/use-cache/constants.ts` | DYNAMIC_EXPIRE(300秒)、RUNTIME_PREFETCH_DYNAMIC_STALE(30秒) |

**読解のコツ**: CacheEntry.valueはReadableStream<Uint8Array>であり、RSCストリームがそのまま保存される点が重要。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | use-cache-wrapper.ts | `packages/next/src/server/use-cache/use-cache-wrapper.ts` | `cache()`関数（836-1766行目）がエントリーポイント |

**主要処理フロー**:
1. **866-871行目**: WorkStore取得とバリデーション
2. **878-974行目**: CacheContext（private/public）の決定
3. **984-1154行目**: キャッシュキー生成とシリアライズ
4. **1234-1460行目**: Resume Data Cacheルックアップ
5. **1462-1736行目**: CacheHandlerルックアップとキャッシュ生成

#### Step 3: キャッシュ生成処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | use-cache-wrapper.ts | `packages/next/src/server/use-cache/use-cache-wrapper.ts` | `generateCacheEntry()`（134-156行目）、`generateCacheEntryImpl()`（520-754行目） |

**主要処理フロー**:
- **147行目**: クリーンなAsyncLocalStorageスナップショットで実行
- **529-582行目**: 引数のデコード
- **591行目**: createLazyResultで遅延実行
- **617-727行目**: prerender/request分岐でRSCストリーム生成
- **731-753行目**: ストリームをteeしてcollectResultでメタデータ収集

#### Step 4: キャッシュハンドラ統合を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | handlers.ts | `packages/next/src/server/use-cache/handlers.ts` | getCacheHandler(), initializeCacheHandlers()のハンドラ管理 |

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

```
cache(kind, id, boundArgsLength, fn, args)
    │
    ├─ getCacheHandler(kind)
    ├─ encodeReply(cacheKeyParts)
    ├─ getRenderResumeDataCache() → RDCルックアップ
    ├─ getPrerenderResumeDataCache() → RDC保存
    ├─ cacheHandler.get(key, softTags)
    │
    ├─ generateCacheEntry()
    │      └─ workStore.runInCleanSnapshot()
    │             └─ workAsyncStorage.run()
    │                    └─ workUnitAsyncStorage.run(cacheStore)
    │                           └─ dynamicAccessAsyncStorage.run()
    │                                  │
    │                                  ├─ decodeReply(encodedArguments)
    │                                  ├─ createLazyResult(fn.bind(args))
    │                                  ├─ prerender() / renderToReadableStream()
    │                                  └─ collectResult()
    │
    ├─ cacheHandler.set(key, pendingEntry)
    └─ createFromReadableStream(stream)
```

### データフロー図

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

fn + args ───▶ キャッシュキー生成
                  │
                  ├─▶ RDC.get() ──── ヒット ──▶ RSCストリーム
                  │
                  ├─▶ CacheHandler.get() ── ヒット ──▶ RSCストリーム
                  │
                  └─▶ generateCacheEntry()
                         │
                         ├─▶ RSCストリーム生成
                         ├─▶ collectResult() → CacheEntry
                         ├─▶ CacheHandler.set()
                         └─▶ RDC.set()
                                                    │
                                          createFromReadableStream()
                                                    │
                                              ──▶ 関数の戻り値
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| use-cache-wrapper.ts | `packages/next/src/server/use-cache/use-cache-wrapper.ts` | ソース | "use cache"の本体実装 |
| handlers.ts | `packages/next/src/server/use-cache/handlers.ts` | ソース | CacheHandler管理 |
| cache-life.ts | `packages/next/src/server/use-cache/cache-life.ts` | ソース | cacheLife() API |
| cache-tag.ts | `packages/next/src/server/use-cache/cache-tag.ts` | ソース | cacheTag() API |
| constants.ts | `packages/next/src/server/use-cache/constants.ts` | ソース | DYNAMIC_EXPIRE等の定数 |
| use-cache-errors.ts | `packages/next/src/server/use-cache/use-cache-errors.ts` | ソース | UseCacheTimeoutError |
| types.ts | `packages/next/src/server/lib/cache-handlers/types.ts` | ソース | CacheEntry/CacheHandler型定義 |
| default.ts | `packages/next/src/server/lib/cache-handlers/default.ts` | ソース | デフォルトCacheHandler実装 |
