# 機能設計書 31-カスタムApp

## 概要

本ドキュメントは、Next.jsのPages RouterにおけるカスタムApp（`_app.tsx`）機能の設計を記述する。カスタムAppは全てのページコンポーネントを包むラッパーとして機能し、グローバルレイアウト・状態管理・ページ初期化を統括する中核機能である。

### 本機能の処理概要

**業務上の目的・背景**：Webアプリケーションでは、全ページに共通するレイアウト（ヘッダー、フッター、サイドバー等）やグローバルな状態管理（認証状態、テーマ設定等）が必要となる。カスタムAppはこれらの横断的関心事を一箇所で管理する仕組みを提供し、コードの重複を排除してメンテナンス性を向上させる。

**機能の利用シーン**：開発者がグローバルCSSの適用、全ページ共通のレイアウトラッパーの設置、ページ遷移時の状態保持（Redux、Context API等）、エラーハンドリング、Web Vitals計測のカスタマイズを行う際に利用される。`pages/_app.tsx`ファイルを作成することでデフォルトのApp動作を上書きできる。

**主要な処理内容**：
1. 全ページコンポーネントのラッピングと初期化処理
2. `getInitialProps`によるサーバーサイドでの初期データ取得
3. ページ遷移間での状態保持（Reactコンポーネントとしてのライフサイクル管理）
4. グローバルCSS・レイアウトの注入
5. `reportWebVitals`によるパフォーマンス計測コールバックの提供

**関連システム・外部連携**：Pages Routerのルーティング機構を通じてページコンポーネントを受け取り、サーバーサイドレンダリング（SSR）と連携して初期データを取得する。Routerインスタンスがpropsとして渡される。

**権限による制御**：カスタムApp自体には権限制御は存在しないが、開発者がカスタムApp内で認証・認可ロジックを実装することで、全ページに対するアクセス制御を実現できる。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 3 | アプリケーションラッパー (_app) | 主画面 | 全ページを包む共通コンポーネントとしてのページ初期化・状態保持処理 |

## 機能種別

初期化処理 / コンポーネントラッピング / データ取得

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| Component | NextComponentType | Yes | レンダリング対象のページコンポーネント | 有効なReactコンポーネントであること |
| pageProps | any | Yes | ページコンポーネントに渡されるprops（getInitialPropsの戻り値） | シリアライズ可能なオブジェクトであること |
| router | Router | Yes | Next.jsルーターインスタンス | 有効なRouterオブジェクトであること |
| __N_SSG | boolean | No | 静的サイト生成（SSG）フラグ | - |
| __N_SSP | boolean | No | サーバーサイドレンダリング（SSP）フラグ | - |

### 入力データソース

- Pages Routerからのページコンポーネントとルーター情報
- `getInitialProps`によるサーバーサイド/クライアントサイドでのデータ取得

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| ReactElement | JSX.Element | ページコンポーネントをラップしたReact要素ツリー |
| pageProps | AppInitialProps | getInitialPropsから返されるページ初期化データ |

### 出力先

- サーバーサイドレンダリングによるHTML出力
- クライアントサイドでのDOM更新

## 処理フロー

### 処理シーケンス

```
1. Next.jsランタイムがページリクエストを受信
   └─ ルーティングによりレンダリング対象ページを特定
2. loadComponentsによるコンポーネント読み込み
   └─ _appモジュールとページモジュールを並列ロード
3. App.getInitialPropsの実行
   └─ カスタムAppが定義されている場合はそのgetInitialPropsを実行
   └─ デフォルトの場合はloadGetInitialPropsでページのgetInitialPropsを呼び出し
4. Appコンポーネントのレンダリング
   └─ Component（ページ）とpagePropsをpropsとして受け取りレンダリング
5. HTMLの生成とクライアントへの送信
   └─ SSR時はサーバーでHTML生成、CSR時はクライアントで仮想DOM更新
```

### フローチャート

```mermaid
flowchart TD
    A[リクエスト受信] --> B[ページコンポーネント特定]
    B --> C[loadComponents実行]
    C --> D{カスタム_appが存在?}
    D -->|Yes| E[カスタムApp.getInitialProps実行]
    D -->|No| F[デフォルトApp.getInitialProps実行]
    E --> G[pagePropsとComponentをAppに渡す]
    F --> G
    G --> H[App.render実行]
    H --> I[Component + pagePropsでページレンダリング]
    I --> J[HTML生成/DOM更新]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-31-01 | getInitialProps空オブジェクト警告 | getInitialPropsが空オブジェクトを返した場合、静的最適化が無効化される旨の警告を出力する | 開発モード時かつgetInitialPropsの戻り値が空オブジェクトの場合 |
| BR-31-02 | インスタンスメソッド禁止 | getInitialPropsをインスタンスメソッドとして定義した場合はエラーをスローする | 開発モード時 |
| BR-31-03 | 静的最適化の無効化 | カスタムAppがgetInitialPropsを定義している場合、自動静的最適化が無効化される | _appにカスタムgetInitialPropsが存在する場合 |

### 計算ロジック

特になし。

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

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

カスタムApp自体はデータベース操作を行わない。開発者がgetInitialProps内でデータベースアクセスを実装することは可能であるが、フレームワーク側の仕様としては該当しない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | Error | getInitialPropsをインスタンスメソッドとして定義した場合 | 静的メソッドとして再定義する |
| - | Error | getInitialPropsがnull/undefinedを返した場合 | 有効なオブジェクトを返すように修正する |
| - | Error | getInitialPropsの戻り値に循環参照が含まれる場合 | シリアライズ可能なオブジェクトを返すように修正する |

### リトライ仕様

リトライは行わない。エラー発生時はエラーページへフォールバックする。

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

該当なし。カスタムAppはトランザクション管理を行わない。

## パフォーマンス要件

- カスタムAppにgetInitialPropsを定義すると自動静的最適化が無効化されるため、不要な場合は定義しないことが推奨される
- getInitialPropsの処理時間はページ全体のTTFBに直接影響する

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

- getInitialPropsで取得したデータはクライアントに送信されるため、機密情報を含めないこと
- カスタムAppはサーバー・クライアント両方で実行されるため、サーバーサイド専用のコードはreq/resの有無で分岐させること

## 備考

- App RouterのLayoutとは異なり、Pages RouterのカスタムAppはページ遷移時にアンマウント・再マウントされない（状態が保持される）
- `reportWebVitals`メソッドをエクスポートすることで、Web Vitalsの計測データを受け取ることができる

---

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

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

### 推奨読解順序

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

まず、カスタムAppに関連する型定義を理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | utils.ts | `packages/next/src/shared/lib/utils.ts` | AppType、AppContextType、AppInitialProps、AppPropsType等の型定義を確認する |

**読解のコツ**: `AppPropsType`は`AppInitialProps`を拡張しており、`Component`（ページコンポーネント）、`router`、SSG/SSPフラグを含む。**31-35行目**で`AppType`が定義され、**163-168行目**で`AppContextType`が定義されている。**170-182行目**の`AppInitialProps`と`AppPropsType`がApp コンポーネントに渡されるpropsの形を規定する。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | _app.tsx | `packages/next/src/pages/_app.tsx` | デフォルトのAppコンポーネント実装を確認する |

**主要処理フロー**:
1. **26-32行目**: `appGetInitialProps`関数 - `loadGetInitialProps`を呼び出してページのgetInitialPropsを実行し、`{ pageProps }`として返す
2. **34-46行目**: `App`クラス - `React.Component`を継承し、`render`メソッドで`Component`と`pageProps`を展開してレンダリングする
3. **38-39行目**: `origGetInitialProps`と`getInitialProps`の両方に`appGetInitialProps`を設定。`origGetInitialProps`はカスタマイズ検出に使用される

#### Step 3: コンポーネント読み込み層を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | load-components.ts | `packages/next/src/server/load-components.ts` | `loadComponentsImpl`での_appモジュール読み込みロジックを確認する |

**主要処理フロー**:
- **182-187行目**: Pages Routerの場合、`requirePage('/_app', distDir, false)`で_appモジュールを読み込む
- **304-305行目**: `interopDefault`でモジュールのデフォルトエクスポートを取得し、`App`として返す

#### Step 4: レンダリング層を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | module.ts | `packages/next/src/server/route-modules/pages/module.ts` | PagesRouteModuleでのApp利用方法を確認する |

**主要処理フロー**:
- **70-82行目**: `PagesComponents`型で`App`と`Document`が定義されている
- **141-153行目**: `render`メソッドで`renderToHTMLImpl`に`this.components.App`を渡している

#### Step 5: getInitialPropsの実行メカニズムを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | utils.ts | `packages/next/src/shared/lib/utils.ts` | `loadGetInitialProps`関数の実装を確認する |

**主要処理フロー**:
- **361-411行目**: `loadGetInitialProps`関数の全体フロー
- **367-373行目**: インスタンスメソッドとして定義された場合のエラーチェック
- **377-385行目**: getInitialPropsが未定義の場合のフォールバック処理
- **387行目**: `App.getInitialProps(ctx)`の実行
- **400-408行目**: 空オブジェクト返却時の警告ログ出力

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

```
PagesRouteModule.render()
    │
    ├─ renderToHTMLImpl()
    │      ├─ loadGetInitialProps(App, ctx)
    │      │      └─ App.getInitialProps(ctx)
    │      │             └─ loadGetInitialProps(Component, ctx.ctx)
    │      │                    └─ Component.getInitialProps(ctx)
    │      │
    │      └─ renderPageTree(App, Component, props)
    │             └─ <App Component={Component} {...props} />
    │
    └─ Document.getInitialProps()
```

### データフロー図

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

リクエスト ──────▶ loadComponents ──▶ App/Component取得
                                            │
ルート情報 ──────▶ loadGetInitialProps ──▶ pageProps取得
                                            │
pageProps + ─────▶ App.render() ───────▶ React要素ツリー
Component                                   │
                                    renderToHTML() ──▶ HTMLレスポンス
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| _app.tsx | `packages/next/src/pages/_app.tsx` | ソース | デフォルトAppコンポーネントの実装 |
| utils.ts | `packages/next/src/shared/lib/utils.ts` | ソース | App関連型定義とloadGetInitialProps |
| load-components.ts | `packages/next/src/server/load-components.ts` | ソース | _appモジュールの読み込み |
| module.ts | `packages/next/src/server/route-modules/pages/module.ts` | ソース | PagesRouteModuleでのAppの利用 |
| render.tsx | `packages/next/src/server/render.tsx` | ソース | ページレンダリング処理 |
