# 機能設計書 61-next/dynamic

## 概要

本ドキュメントは、Next.jsの`next/dynamic`機能の設計について記述する。`next/dynamic`は動的インポート（Dynamic Import）を利用したコード分割と遅延読み込みを提供するAPIであり、React.lazyとSuspenseを基盤としてコンポーネント単位のコード分割を実現する。

### 本機能の処理概要

`next/dynamic`は、コンポーネントを動的にインポートすることで、初回ページロード時のJavaScriptバンドルサイズを削減し、パフォーマンスを向上させる機能である。

**業務上の目的・背景**：Webアプリケーションの初回ロード速度はユーザー体験に直結する。大規模なアプリケーションでは、すべてのコンポーネントを一括でバンドルすると初回ロードが遅くなる。`next/dynamic`はコンポーネント単位でコード分割を行い、必要なタイミングでのみコードを読み込むことで、この課題を解決する。

**機能の利用シーン**：重いライブラリに依存するコンポーネント（エディター、チャート、地図など）を遅延読み込みする場合や、SSRが不要なクライアントサイド専用コンポーネント（ブラウザAPIに依存するもの）を条件付きで読み込む場合に利用される。

**主要な処理内容**：
1. `dynamic()`関数でコンポーネントのローダー関数を受け取り、React.lazyコンポーネントとしてラップする
2. `ssr: false`オプション指定時にサーバーサイドレンダリングをバイパスし、クライアントサイドのみで描画する
3. ローディングコンポーネントの表示制御（Suspenseフォールバック）
4. SSR時のCSS先読み（PreloadChunks）によりフラッシュオブアンスタイルドコンテンツ（FOUC）を防止する

**関連システム・外部連携**：Webpack/Turbopackのコード分割機能と連携し、react-loadableマニフェストを介してチャンク情報を管理する。

**権限による制御**：特になし。全てのコンポーネントから利用可能。

## 関連画面

本機能は特定の画面に紐づくものではなく、任意のページ・コンポーネントから利用される汎用的なユーティリティ機能である。

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | 任意のページ/コンポーネント | 利用元 | dynamicインポートによるコンポーネントの遅延読み込み |

## 機能種別

コード分割 / 遅延読み込み / レンダリング制御

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| dynamicOptions | `DynamicOptions<P> \| Loader<P>` | Yes | ローダー関数またはオプションオブジェクト | Promise、関数、またはオブジェクトであること |
| options | `DynamicOptions<P>` | No | 追加のオプション（loadingコンポーネント、ssr制御等） | - |
| options.loading | `(props: DynamicOptionsLoadingProps) => ReactNode` | No | ローディング中に表示するコンポーネント | - |
| options.ssr | `boolean` | No | SSRを行うかどうか（デフォルト: true） | boolean型 |
| options.loader | `Loader<P>` | No | コンポーネントのローダー関数 | 関数またはPromise |

### 入力データソース

開発者がソースコード中で`next/dynamic`をインポートし、動的にロードしたいコンポーネントのパスを指定する。

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| LoadableComponent | `React.ComponentType<P>` | 遅延読み込み対応のReactコンポーネント |

### 出力先

動的インポートされたコンポーネントは、React DOMツリー内に描画される。SSR時はPreloadChunksコンポーネントが関連CSSのlink要素をHTMLに出力する。

## 処理フロー

### 処理シーケンス

```
1. dynamic()関数の呼び出し
   └─ dynamicOptionsの型に応じてloaderを正規化
2. loadableGeneratedオプションのマージ
   └─ Babelプラグインにより生成されたwebpack/modules情報を統合
3. SSR制御の判定
   └─ ssr: falseの場合はnoSSR()でクライアントサイド専用コンポーネントを返却
4. Loadable関数の呼び出し
   └─ React.lazy()でローダーを遅延コンポーネント化
5. LoadableComponentの構築
   └─ SSR有効時: PreloadChunks + Lazy コンポーネント
   └─ SSR無効時: BailoutToCSR + Lazy コンポーネント
6. Suspenseバウンダリのラップ
   └─ loadingコンポーネントまたはSSR無効時にSuspenseで包む
```

### フローチャート

```mermaid
flowchart TD
    A[dynamic関数呼び出し] --> B{dynamicOptionsの型}
    B -->|Promise| C[loader = dynamicOptions]
    B -->|Function| D[loader = dynamicOptions]
    B -->|Object| E[loadableOptions = dynamicOptions]
    C --> F[loadableGeneratedマージ]
    D --> F
    E --> F
    F --> G{ssr === false?}
    G -->|Yes| H[noSSR: クライアント専用コンポーネント生成]
    G -->|No| I[Loadable: React.lazy + Suspenseラップ]
    H --> J[コンポーネント返却]
    I --> K{SSR実行中?}
    K -->|Yes| L[PreloadChunks + Lazyコンポーネント]
    K -->|No| M[Lazyコンポーネント]
    L --> J
    M --> J
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-61-01 | SSR無効化時のサーバー描画 | ssr: false指定時、サーバーサイドではloadingコンポーネントのみを描画する | dynamicオプションでssr: falseが指定された場合 |
| BR-61-02 | モジュール正規化 | default exportを持つモジュールも、コンポーネント直接exportも両方サポートする | loaderがPromiseを解決した時 |
| BR-61-03 | Suspenseバウンダリ自動付与 | loading指定時またはssr:false時に自動的にSuspenseバウンダリを付与する | loadingが指定されているか、ssrがfalseの場合 |
| BR-61-04 | CSS先読み | SSR時にreact-loadableマニフェストからCSSファイルを抽出しpreloadする | SSR有効時かつmodulesが指定されている場合 |

### 計算ロジック

特になし。

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

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

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| BAILOUT_TO_CSR | BailoutToCSRError | ssr: falseのコンポーネントがサーバーサイドで描画されようとした場合 | Next.jsのエラーバウンダリがキャッチし、クライアントサイドレンダリングにフォールバック |
| MODULE_LOAD_ERROR | ランタイムエラー | 動的インポートのモジュール解決に失敗した場合 | loadingコンポーネントのerrorプロパティとして伝播 |

### リトライ仕様

DynamicOptionsLoadingPropsにretry関数が含まれるが、現在のデフォルト実装ではリトライ機構は提供されていない。

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

該当なし。

## パフォーマンス要件

- 動的インポートされたコンポーネントは、初回アクセス時にネットワークリクエストが発生する
- SSR時のCSS先読みにより、クライアントサイドでのFOUCを防止する
- スクリプトのプリロードはfetchPriority: 'low'で行われる

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

- PreloadChunksコンポーネントはnonceをサポートし、Content Security Policy（CSP）に対応する
- デプロイメントIDをクエリパラメータとして付与し、CDNキャッシュの整合性を保つ

## 備考

- `next/dynamic`はApp RouterとPages Routerの両方で利用可能
- App Routerでは`React.lazy()`と`Suspense`の直接使用も推奨されている
- `loadableGenerated`オプションはBabelプラグイン（react-loadable-plugin）が自動的に挿入する

---

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

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

### 推奨読解順序

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

まず、`next/dynamic`で使用される型定義を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | types.ts | `packages/next/src/shared/lib/lazy-dynamic/types.ts` | ComponentModule、Loader、LoaderComponent、DynamicOptionsLoadingPropsの型定義を確認 |

**読解のコツ**: `ComponentModule<P>`は`{ default: React.ComponentType<P> }`という構造であり、ESモジュールのdefault exportを表現している。`Loader<P>`は`() => Promise<ComponentType | ComponentModule>`というファクトリ関数型。

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

`next/dynamic`のメインAPIであるdynamic関数の処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | dynamic.tsx | `packages/next/src/shared/lib/dynamic.tsx` | dynamic()関数のオーバーロード解決、オプション正規化、noSSR分岐を理解 |

**主要処理フロー**:
1. **79-82行目**: dynamic関数の定義。dynamicOptionsとoptionsの2引数を受け取る
2. **85-105行目**: デフォルトloadingコンポーネントの定義。pastDelay前はnull、開発モードではエラー表示
3. **111-119行目**: dynamicOptionsの型判定（Promise/Function/Object）に応じたloader設定
4. **124-128行目**: loaderの正規化。convertModuleでdefault exportを統一形式に変換
5. **131-137行目**: loadableGenerated（Babelプラグイン生成）のマージ
6. **140-145行目**: ssr: false判定とnoSSR関数呼び出し

#### Step 3: Loadableコンポーネントの実装を理解する

React.lazyとSuspenseを使った遅延読み込みの実装。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | loadable.tsx | `packages/next/src/shared/lib/lazy-dynamic/loadable.tsx` | Loadable関数がReact.lazyとSuspenseを組み合わせてLoadableComponentを生成する流れ |

**主要処理フロー**:
- **41-43行目**: Loadable関数。optsをマージし、React.lazy()でLazyコンポーネントを生成
- **46-69行目**: LoadableComponent。SSR有無でPreloadChunksまたはBailoutToCSRを選択し、Suspenseバウンダリで包む
- **52-54行目**: Suspenseバウンダリの条件判定。SSR無効またはloading指定時にSuspenseでラップ

#### Step 4: SSRバイパス機構を理解する

`ssr: false`指定時のクライアントサイドレンダリングへのフォールバック機構。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | bailout-to-csr.ts | `packages/next/src/shared/lib/lazy-dynamic/bailout-to-csr.ts` | BailoutToCSRError クラスの定義。digestプロパティでエラー識別 |
| 4-2 | dynamic-bailout-to-csr.tsx | `packages/next/src/shared/lib/lazy-dynamic/dynamic-bailout-to-csr.tsx` | BailoutToCSRコンポーネント。サーバーサイドでBailoutToCSRErrorをthrowする |

**主要処理フロー**:
- **bailout-to-csr.ts 5-11行目**: BailoutToCSRErrorクラス。digestに'BAILOUT_TO_CLIENT_SIDE_RENDERING'を設定
- **dynamic-bailout-to-csr.tsx 15-21行目**: サーバーサイド（window===undefined）でエラーをthrow、クライアントサイドではchildrenをそのまま返す

#### Step 5: CSS先読み機構を理解する

SSR時のCSS先読み処理。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | preload-chunks.tsx | `packages/next/src/shared/lib/lazy-dynamic/preload-chunks.tsx` | react-loadableマニフェストからCSSファイルを抽出し、linkタグで先読みする |

**主要処理フロー**:
- **19-20行目**: workAsyncStorageからworkStoreを取得
- **28-35行目**: reactLoadableManifestからmoduleIdsに対応するファイルを抽出
- **43-73行目**: CSSファイルにはlinkタグ（precedence="dynamic"）、JSファイルにはReactDOM.preloadを使用

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

```
dynamic() [dynamic.tsx]
    │
    ├─ convertModule() [dynamic.tsx:34]
    │      └─ モジュール形式の正規化（default export統一）
    │
    ├─ noSSR() [dynamic.tsx:53] ※ssr: false時
    │      └─ Loadable() or Loading コンポーネント返却
    │
    └─ Loadable() [loadable.tsx:41]
           │
           ├─ React.lazy()
           │      └─ loader().then(convertModule)
           │
           └─ LoadableComponent [loadable.tsx:46]
                  │
                  ├─ PreloadChunks [preload-chunks.tsx:9] ※SSR時
                  │      └─ workAsyncStorage.getStore()
                  │      └─ reactLoadableManifest参照
                  │
                  └─ BailoutToCSR [dynamic-bailout-to-csr.tsx:15] ※ssr:false時
                         └─ BailoutToCSRError throw（サーバーサイド）
```

### データフロー図

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

dynamic(() =>            ┌─ dynamic()                         LoadableComponent
  import('./MyComp'))    │    オプション正規化                    (React.ComponentType)
       │                 │    │
       ▼                 │    ▼
DynamicOptions ────────▶ │  Loadable()                        ┌─ <Suspense>
  loader: Function       │    React.lazy(loader)              │    <PreloadChunks/>  ※SSR
  ssr: boolean           │    │                               │    <Lazy {...props}/>
  loading: Component     │    ▼                               └─ </Suspense>
                         │  LoadableComponent
                         │    ├─ SSR有効 → PreloadChunks + Lazy
                         │    └─ SSR無効 → BailoutToCSR + Lazy
                         └─────────────────────────────────────▶ 描画結果
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| dynamic.tsx | `packages/next/src/shared/lib/dynamic.tsx` | ソース | dynamic()関数のメインエントリーポイント（Pages Router向け互換レイヤー） |
| loadable.tsx | `packages/next/src/shared/lib/lazy-dynamic/loadable.tsx` | ソース | React.lazy + Suspenseベースのloadable実装（App Router向け） |
| types.ts | `packages/next/src/shared/lib/lazy-dynamic/types.ts` | ソース | 型定義 |
| bailout-to-csr.ts | `packages/next/src/shared/lib/lazy-dynamic/bailout-to-csr.ts` | ソース | BailoutToCSRErrorクラス定義 |
| dynamic-bailout-to-csr.tsx | `packages/next/src/shared/lib/lazy-dynamic/dynamic-bailout-to-csr.tsx` | ソース | SSRバイパス用クライアントコンポーネント |
| preload-chunks.tsx | `packages/next/src/shared/lib/lazy-dynamic/preload-chunks.tsx` | ソース | SSR時のCSS/JSチャンク先読み |
