# 機能設計書 58-Image Optimizer

## 概要

本ドキュメントは、Next.jsのImage Optimizerの機能設計を記述する。Image Optimizerはサーバーサイドで画像のリサイズ・フォーマット変換・キャッシュを実行する`/_next/image`エンドポイントの処理エンジンであり、`next/image`コンポーネントと連携して最適化された画像を配信する。

### 本機能の処理概要

**業務上の目的・背景**：モダンWebにおいて、WebPやAVIF等の効率的な画像フォーマットの使用、適切なサイズへのリサイズ、キャッシュの活用は画像配信の基本要件である。しかし、これらをアプリケーション開発者が個別に実装・運用するのは大きな負担となる。Image Optimizerはこれらの処理をNext.jsのサーバーに統合し、`/_next/image?url=...&w=...&q=...`というシンプルなAPIで画像最適化を提供する。

**機能の利用シーン**：`next/image`コンポーネントがsrcを`/_next/image`エンドポイント経由のURLに変換する際にImage Optimizerが呼び出される。ローカル画像およびリモート画像（remotePatterns設定に基づく）の両方を処理する。

**主要な処理内容**：
1. リクエストパラメータのバリデーション（url, w, q）
2. ローカル/リモート画像のアクセス制御（localPatterns, remotePatterns, domains）
3. 画像ファイルの取得（ローカルファイルまたはHTTPフェッチ）
4. コンテンツタイプの自動検出（マジックナンバー解析）
5. sharp/squooshによる画像リサイズ・フォーマット変換
6. キャッシュの管理（ファイルシステムキャッシュまたはIncrementalCache）
7. ETag生成とキャッシュレスポンス（304 Not Modified対応）
8. アニメーション画像の検出とバイパス

**関連システム・外部連携**：sharp（画像処理ライブラリ）、IncrementalCache、ファイルシステムキャッシュ、リモート画像サーバー。

**権限による制御**：`localPatterns`と`remotePatterns`（next.config.js）による画像URLのアクセス制御。プライベートIP検出による内部リソース保護。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | 該当なし | - | サーバーサイドAPIエンドポイントのため特定画面なし |

## 機能種別

画像処理 / APIエンドポイント / キャッシュ管理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| url | string | Yes | 画像ソースURL（ローカルパスまたは絶対URL） | 3072文字以下、再帰的URLでない、localPatterns/remotePatterns許可 |
| w | string | Yes | 出力幅（ピクセル） | 正の整数、deviceSizes+imageSizesの許可サイズ |
| q | string | Yes | 品質（1-100） | 1-100の整数、qualities設定がある場合は許可値のみ |
| Accept (ヘッダー) | string | No | 受け入れ可能なMIMEタイプ | AVIF/WebPのサポート判定に使用 |

### 入力データソース

- HTTPリクエストのクエリパラメータとヘッダー
- next.config.jsのimages設定（deviceSizes, imageSizes, formats, remotePatterns, localPatterns, minimumCacheTTL, qualities）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| 最適化画像バイナリ | Buffer | リサイズ・フォーマット変換された画像データ |
| Content-Type | string | 出力画像のMIMEタイプ（image/webp, image/avif等） |
| Cache-Control | string | キャッシュ制御ヘッダー |
| ETag | string | 画像のハッシュ値 |
| X-Nextjs-Cache | string | キャッシュヒット状態（HIT/MISS/STALE） |

### 出力先

- HTTPレスポンスとしてクライアントに配信
- ファイルシステムキャッシュ（.next/cache/images/）

## 処理フロー

### 処理シーケンス

```
1. パラメータバリデーション (validateParams)
   ├─ url: 必須、3072文字制限、プロトコル検証
   ├─ w: 必須、許可サイズチェック
   ├─ q: 必須、1-100範囲チェック
   ├─ ローカルURL: localPatterns照合
   └─ リモートURL: remotePatterns/domains照合
2. MIMEタイプ判定
   └─ Acceptヘッダーからサポートフォーマット判定（AVIF > WebP）
3. キャッシュ参照
   ├─ getCacheKey でキャッシュキー生成
   └─ キャッシュHIT: 304/画像レスポンス
4. 画像取得 (キャッシュMISS時)
   ├─ ローカル: ファイルシステムから読み込み
   └─ リモート: HTTPフェッチ（プライベートIP検出あり）
5. コンテンツタイプ検出 (detectContentType)
   └─ マジックナンバー解析（JPEG, PNG, WebP, AVIF等）
6. 画像最適化
   ├─ バイパス判定（SVG, ICO, BMP, JXL, HEIC）
   ├─ アニメーション判定（WebP, PNG, GIF）
   └─ sharp による リサイズ + フォーマット変換
7. キャッシュ書き込み
   └─ writeToCacheDir でファイルシステムに保存
8. レスポンス生成
   ├─ Content-Type, Cache-Control, ETag ヘッダー
   └─ 画像バイナリ送信
```

### フローチャート

```mermaid
flowchart TD
    A[/_next/image リクエスト] --> B[validateParams]
    B --> C{バリデーション OK?}
    C -->|No| D[400 エラーレスポンス]
    C -->|Yes| E[getCacheKey]
    E --> F{キャッシュ HIT?}
    F -->|HIT| G{ETag 一致?}
    G -->|Yes| H[304 Not Modified]
    G -->|No| I[キャッシュ画像レスポンス]
    F -->|MISS| J{ローカル or リモート?}
    J -->|ローカル| K[ファイルシステム読み込み]
    J -->|リモート| L[HTTP フェッチ]
    K --> M[detectContentType]
    L --> M
    M --> N{バイパス対象?}
    N -->|Yes| O[そのまま配信]
    N -->|No| P[sharp リサイズ + 変換]
    P --> Q[writeToCacheDir]
    Q --> R[画像レスポンス]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-58-01 | サイズ制限 | 出力幅はdeviceSizes+imageSizesの許可値のみ | 常時（開発モードではBLUR_IMG_SIZE追加） |
| BR-58-02 | フォーマット優先度 | Accept headerに基づきAVIF > WebP > 元フォーマット | formats設定に依存 |
| BR-58-03 | バイパスフォーマット | SVG, ICO, ICNS, BMP, JXL, HEICは最適化をバイパス | 該当フォーマット検出時 |
| BR-58-04 | アニメーション検出 | アニメーション画像（WebP, PNG, GIF）は最適化をバイパス | isAnimated判定 |
| BR-58-05 | プライベートIP保護 | リモート画像のフェッチ先がプライベートIPの場合はブロック | リモート画像フェッチ時 |
| BR-58-06 | キャッシュTTL | minimumCacheTTLとupstreamのCache-Controlで決定 | 常時 |
| BR-58-07 | sharp並列度制限 | 開発モードでは並列度をさらに制限（4分の1） | process.env.NODE_ENV === 'development' |
| BR-58-08 | 再帰防止 | /_next/imageへの再帰的リクエストを拒否 | urlパラメータ検査時 |

### 計算ロジック

- キャッシュキー: `SHA256(CACHE_VERSION, href, width, quality, mimeType)` のbase64urlハッシュ
- ETag: `SHA256(imageBuffer)` のbase64urlハッシュ
- キャッシュファイル名: `{maxAge}.{expireAt}.{etag}.{upstreamEtag}.{extension}`

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

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

| 操作 | 対象 | 操作種別 | 概要 |
|-----|------|---------|------|
| キャッシュ読み込み | .next/cache/images/ | SELECT | キャッシュキーに対応するファイルの読み込み |
| キャッシュ書き込み | .next/cache/images/ | INSERT | 最適化画像のファイルシステムへの書き込み |
| キャッシュ削除 | .next/cache/images/ | DELETE | 古いキャッシュディレクトリの再帰削除 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 400 | バリデーションエラー | url/w/qパラメータ不正 | errorMessageをレスポンス |
| 400 | アクセス制御エラー | localPatterns/remotePatternsに不一致 | '"url" parameter is not allowed' |
| 500 | 画像処理エラー | sharpでの変換失敗 | エラーログ出力 |
| - | モジュール未検出 | sharpがインストールされていない | 'Module `sharp` not found'エラー |

### リトライ仕様

リトライは行わない。キャッシュのstale状態でのレスポンスはサポート。

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

ファイルシステムキャッシュの書き込みは、ディレクトリの再帰削除→再作成→ファイル書き込みの順序で実行される。

## パフォーマンス要件

- sharpの並列度制御（本番: CPU/2、開発: CPU/4）
- CACHE_VERSION管理によるキャッシュ無効化
- minimumCacheTTLによるキャッシュ期間制御（デフォルト14400秒 = 4時間）
- ETag/304レスポンスによるネットワーク帯域削減

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

- localPatterns/remotePatternsによる画像URLの厳密なアクセス制御
- プライベートIPチェック（isPrivateIp）によるSSRF防止
- URL長さ制限（3072文字）による潜在的な攻撃の防止
- プロトコル検証（http:/https:のみ許可）
- 再帰的リクエスト防止

## 備考

- CACHE_VERSIONは現在4
- BLUR_IMG_SIZE = 8、BLUR_QUALITY = 70（next-image-loaderと一致）
- domains設定は非推奨でremotePatternsへの移行が推奨される
- DNS lookupによるIPアドレス解決でプライベートIP検出を実行

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | image-optimizer.ts | `packages/next/src/server/image-optimizer.ts` | **40-59行目**: 定数定義 - 画像MIMEタイプ、CACHE_VERSION、BYPASS_TYPES、ANIMATABLE_TYPES |
| 1-2 | image-optimizer.ts | `packages/next/src/server/image-optimizer.ts` | **90-99行目**: `ImageParamsResult`型 - href, isAbsolute, isStatic, width, quality, mimeType |

**読解のコツ**: 画像フォーマットの判定はマジックナンバー（ファイル先頭バイト列）に基づいており、detectContentType関数で多数のフォーマットに対応している。

#### Step 2: パラメータバリデーションを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | image-optimizer.ts | `packages/next/src/server/image-optimizer.ts` | **322-482行目**: `ImageOptimizerCache.validateParams` - 入力パラメータの完全なバリデーション |

**主要処理フロー**:
1. **328-335行目**: 設定値の取得（deviceSizes, imageSizes, domains等）
2. **348-399行目**: URL検証 - ローカル/リモート判定、パターンマッチング
3. **401-443行目**: 幅(w)検証 - 許可サイズリストとの照合
4. **445-464行目**: 品質(q)検証 - 1-100範囲、qualities設定チェック
5. **466行目**: MIMEタイプ判定 - Acceptヘッダーとformats設定

#### Step 3: コンテンツタイプ検出を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | image-optimizer.ts | `packages/next/src/server/image-optimizer.ts` | **167-314行目**: `detectContentType` - マジックナンバーによるフォーマット検出 |

#### Step 4: sharp統合とキャッシュ管理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | image-optimizer.ts | `packages/next/src/server/image-optimizer.ts` | **64-88行目**: `getSharp` - sharp動的読み込みと並列度制御 |
| 4-2 | image-optimizer.ts | `packages/next/src/server/image-optimizer.ts` | **141-160行目**: `writeToCacheDir` - キャッシュファイル書き込み |
| 4-3 | image-optimizer.ts | `packages/next/src/server/image-optimizer.ts` | **484-496行目**: `getCacheKey` - SHA256ハッシュキー生成 |

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

```
/_next/image リクエスト
    |
    +-- ImageOptimizerCache.validateParams()
    |       +-- hasLocalMatch() / hasRemoteMatch()
    |       +-- getSupportedMimeType()
    |
    +-- ImageOptimizerCache.getCacheKey()
    |       +-- getHash()
    |
    +-- キャッシュ参照
    |       +-- readFromCacheDir() / IncrementalCache
    |
    +-- 画像取得（キャッシュMISS時）
    |       +-- ローカル: fs.readFile()
    |       +-- リモート: fetch() + isPrivateIp()
    |
    +-- detectContentType()
    |       +-- detector() (image-detector)
    |       +-- sharp().metadata()
    |
    +-- sharp() リサイズ + 変換
    |       +-- getSharp()
    |
    +-- writeToCacheDir()
    +-- sendEtagResponse()
```

### データフロー図

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

HTTP Request (?url,w,q) --> validateParams() ------------> ImageParamsResult
Accept Header -----------> getSupportedMimeType() -------> mimeType
                            |
画像ファイル/URL ----------> detectContentType() ----------> contentType
                            |
画像バイナリ -------------- > sharp() resize+convert ------> 最適化バイナリ
                            |
                            +-> writeToCacheDir() ---------> FS キャッシュ
                            +-> HTTP Response -------------> クライアント
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| image-optimizer.ts | `packages/next/src/server/image-optimizer.ts` | ソース | Image Optimizer本体（1214行） |
| match-local-pattern.ts | `packages/next/src/shared/lib/match-local-pattern.ts` | ソース | ローカルパターンマッチング |
| match-remote-pattern.ts | `packages/next/src/shared/lib/match-remote-pattern.ts` | ソース | リモートパターンマッチング |
| is-private-ip.ts | `packages/next/src/server/is-private-ip.ts` | ソース | プライベートIP検出 |
| image-config.ts | `packages/next/src/shared/lib/image-config.ts` | ソース | 画像設定型・デフォルト値 |
| send-payload.ts | `packages/next/src/server/send-payload.ts` | ソース | ETagレスポンス送信 |
| serve-static.ts | `packages/next/src/server/serve-static.ts` | ソース | Content-Type/拡張子取得 |
