# 機能設計書 3-ルーティング

## 概要

本ドキュメントは、Decap CMSのクライアントサイドルーティング機能の設計を記載する。ハッシュベースのルーティングにより、コレクション一覧、エントリ編集、新規作成、ワークフロー画面へのナビゲーションを提供する。

### 本機能の処理概要

本機能は、ハッシュベースのクライアントサイドルーティングを実装し、SPA（Single Page Application）としてのDecap CMS内のページ遷移を管理する。`createHashHistory`によるhistoryオブジェクトとReact RouterのSwitch/Routeコンポーネントにより、URLパスに応じた画面コンポーネントの表示切り替えを行う。

**業務上の目的・背景**：Decap CMSはSPAとして動作するため、サーバー側のルーティング設定なしにブラウザのURLを介して画面遷移を管理する必要がある。ハッシュベースのルーティング（`#/collections/...`形式）を採用することで、静的ファイルホスティング環境でもリロード時のルーティングが正しく動作する。

**機能の利用シーン**：ユーザーがCMS管理画面内でコレクションの選択、エントリの閲覧・編集、新規作成、ワークフロー画面の表示、検索結果の表示などを行う際に、すべてのナビゲーションでこのルーティング機能が使用される。

**主要な処理内容**：
1. ハッシュベースhistoryオブジェクトの作成（`createHashHistory`）
2. React Router（`Router`, `Switch`, `Route`）によるパスマッチング
3. 各画面コンポーネントへのルーティング（Collection, Editor, Workflow, NotFoundPage）
4. コレクション存在チェック付きルーティング（`RouteInCollection`）
5. プログラマティックナビゲーション関数（`navigateToCollection`, `navigateToEntry`, `navigateToNewEntry`）
6. デフォルトパスへのリダイレクト

**関連システム・外部連携**：特になし（クライアントサイド完結）。

**権限による制御**：ルーティング自体には権限制御はないが、App.jsのrender内で認証状態による分岐が行われ、未認証ユーザーにはログイン画面が表示される。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 11 | 404 Not Found画面 | 結果表示画面 | React RouterのSwitch最後のフォールバックルートとしてNotFoundPageコンポーネントを表示 |

## 機能種別

ナビゲーション / ルーティング

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| URL | string (hash) | Yes | ブラウザのハッシュURL（例: `#/collections/posts`） | React Routerのパスマッチング |
| collectionName | string | Yes（ナビ関数） | navigateToCollection等のコレクション名 | コレクション存在チェック（RouteInCollection） |
| slug | string | Yes（entryナビ） | navigateToEntryのエントリスラッグ | - |

### 入力データソース

- ブラウザのハッシュURL（`location.hash`）
- ユーザーのリンククリックやプログラマティックナビゲーション

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| 画面コンポーネント | ReactElement | URLパスに対応するReactコンポーネント |

### 出力先

- ブラウザDOM（AppMainContainer内のSwitch領域）

## 処理フロー

### 処理シーケンス

```
1. ブラウザURLのハッシュ部分を解析
2. React RouterのSwitchコンポーネントが順番にRouteをマッチング
   └─ "/" -> デフォルトコレクションへリダイレクト
   └─ "/collections/:name" -> Collection画面
   └─ "/collections/:name/new" -> Editor画面（新規作成）
   └─ "/collections/:name/entries/*" -> Editor画面（編集）
   └─ "/collections/:name/search/:searchTerm" -> Collection画面（検索結果）
   └─ "/collections/:name/filter/:filterTerm*" -> Collection画面（フィルタ）
   └─ "/workflow" -> Workflow画面（editorial_workflow時のみ）
   └─ "/search/:searchTerm" -> Collection画面（全体検索）
   └─ "/edit/:name/:entryName" -> エントリ編集へリダイレクト
   └─ マッチなし -> NotFoundPage
3. RouteInCollectionコンポーネントがコレクション存在チェック
   └─ 存在する -> 対象コンポーネントをレンダリング
   └─ 存在しない -> デフォルトパスへリダイレクト
```

### フローチャート

```mermaid
flowchart TD
    A[URLハッシュ変更] --> B{パスマッチング}
    B -->|/| C[デフォルトコレクションへリダイレクト]
    B -->|/collections/:name| D{コレクション存在?}
    D -->|Yes| E[Collection画面表示]
    D -->|No| C
    B -->|/collections/:name/new| F{コレクション存在?}
    F -->|Yes| G[Editor画面 newRecord表示]
    F -->|No| C
    B -->|/collections/:name/entries/*| H{コレクション存在?}
    H -->|Yes| I[Editor画面 編集表示]
    H -->|No| C
    B -->|/workflow| J[Workflow画面表示]
    B -->|/search/:searchTerm| K[全体検索結果表示]
    B -->|マッチなし| L[NotFoundPage表示]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | デフォルトリダイレクト | ルート（/）アクセス時は最初の非非表示コレクションにリダイレクト | "/"パスアクセス時 |
| BR-02 | コレクション存在チェック | 指定されたコレクション名が存在しない場合はデフォルトパスにリダイレクト | /collections/:nameパスアクセス時 |
| BR-03 | ワークフローパス有効化 | /workflowパスはpublish_mode=editorial_workflowの場合のみ有効 | Editorial Workflow設定時 |
| BR-04 | 旧形式URLリダイレクト | /edit/:name/:entryNameは/collections/:name/entries/:entryNameにリダイレクト | 旧形式URLアクセス時 |
| BR-05 | 非表示コレクション除外 | hide=trueのコレクションはデフォルトパス候補から除外される | デフォルトパス算出時 |

### 計算ロジック

デフォルトパスの算出: `collections.filter(c => c.get('hide') !== true).first()` の `name` を使用して `/collections/{name}` を生成。

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

本機能はクライアントサイドルーティングのため、データベース操作はない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | 404エラー | どのルートにもマッチしないURL | NotFoundPageコンポーネントを表示 |
| Error | コレクション不在 | 非表示でないコレクションが1つも存在しない | 'Could not find a non hidden collection'エラーをthrow |
| - | コレクション不一致 | URLのコレクション名がReduxストアに存在しない | デフォルトパスへリダイレクト |

### リトライ仕様

ルーティングエラーに対するリトライは不要。

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

該当なし。

## パフォーマンス要件

- URLハッシュ変更からコンポーネントレンダリングまでの遅延は最小限であること
- React Routerの標準的なマッチング性能に依存

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

- ハッシュベースルーティングのため、URLパラメータはサーバーに送信されない
- 認証状態のチェックはApp.jsのrender内で行われ、未認証ユーザーにはルーティングされた画面は表示されない
- URLに含まれるパラメータの直接的なサニタイズは行われていないが、React Routerのマッチパラメータとして安全に処理される

## 備考

- React Router v5（react-router-dom）を使用
- `history`パッケージの`createHashHistory`でhistoryオブジェクトを作成
- `navigateToEntry`と`navigateToNewEntry`は`history.replace`を使用（ブラウザ履歴に追加しない）
- `navigateToCollection`は`history.push`を使用（ブラウザ履歴に追加する）

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | history.ts | `packages/decap-cms-core/src/routing/history.ts` | hashHistoryの作成とナビゲーション関数の定義 |

**読解のコツ**: `createHashHistory`はURLの`#`以降を使ってルーティングを行う。`history.push`は新しい履歴エントリを追加し、`history.replace`は現在の履歴エントリを置き換える。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | bootstrap.js | `packages/decap-cms-core/src/bootstrap.js` | Router コンポーネントへのhistoryオブジェクト渡し |

**主要処理フロー**:
- **10行目**: `history`のインポート
- **27行目**: `<Router history={history}>`でhistoryオブジェクトをRouterに渡す

#### Step 3: ルーティング定義を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | App.js | `packages/decap-cms-core/src/components/App/App.js` | Switchコンポーネント内のRoute定義全体 |

**主要処理フロー**:
- **50-57行目**: getDefaultPath関数 - 最初の非非表示コレクションのパスを返す
- **59-70行目**: RouteInCollectionコンポーネント - コレクション存在チェック付きRoute
- **198-253行目**: Switch内のRoute定義一覧:
  - **199行目**: ルート -> デフォルトパスへリダイレクト
  - **200行目**: /search/ -> デフォルトパスへリダイレクト
  - **201-206行目**: /collections/:name/search/ -> コレクションへリダイレクト
  - **213行目**: /workflow -> Workflowコンポーネント（hasWorkflow条件）
  - **214-219行目**: /collections/:name -> Collectionコンポーネント
  - **220-223行目**: /collections/:name/new -> Editor(newRecord)コンポーネント
  - **224-229行目**: /collections/:name/entries/* -> Editorコンポーネント
  - **230-234行目**: /collections/:name/search/:searchTerm -> Collection(検索結果)
  - **235-239行目**: /collections/:name/filter/:filterTerm* -> Collection(フィルタ)
  - **240-243行目**: /search/:searchTerm -> Collection(全体検索)
  - **244-251行目**: /edit/:name/:entryName -> entriesパスへリダイレクト
  - **252行目**: フォールバック -> NotFoundPage

#### Step 4: ナビゲーション関数を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | history.ts | `packages/decap-cms-core/src/routing/history.ts` | navigateTo*関数群 |

**主要処理フロー**:
- **5-7行目**: navigateToCollection - `history.push('/collections/{name}')`
- **9-11行目**: navigateToNewEntry - `history.replace('/collections/{name}/new')`
- **13-15行目**: navigateToEntry - `history.replace('/collections/{name}/entries/{slug}')`

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

```
bootstrap()
    |
    +-- Router(history)
            |
            +-- App
                    |
                    +-- Switch
                            |
                            +-- Redirect (/ -> defaultPath)
                            +-- RouteInCollection (/collections/:name)
                            |       +-- Collection
                            +-- RouteInCollection (/collections/:name/new)
                            |       +-- Editor (newRecord)
                            +-- RouteInCollection (/collections/:name/entries/*)
                            |       +-- Editor
                            +-- Route (/workflow)
                            |       +-- Workflow
                            +-- Route (/search/:searchTerm)
                            |       +-- Collection (isSearchResults)
                            +-- Route (fallback)
                                    +-- NotFoundPage

navigateToCollection(name) ---> history.push()
navigateToNewEntry(name) -----> history.replace()
navigateToEntry(name, slug) --> history.replace()
```

### データフロー図

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

ブラウザURL ---------> hashHistory解析 ----+
(location.hash)                             |
                                            v
                                    React Router Switch
                                            |
                                    パスマッチング
                                            |
                                    +-------+-------+
                                    |       |       |
                              Collection  Editor  Workflow
                                    |
                              ブラウザDOM更新

プログラマティック
ナビゲーション -------> history.push/replace -> URL変更 -> 再マッチング
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| history.ts | `packages/decap-cms-core/src/routing/history.ts` | ソース | hashHistoryの作成とナビゲーション関数 |
| App.js | `packages/decap-cms-core/src/components/App/App.js` | ソース | Switch/Route定義、RouteInCollectionコンポーネント |
| bootstrap.js | `packages/decap-cms-core/src/bootstrap.js` | ソース | Routerへのhistoryオブジェクト渡し |
| NotFoundPage.js | `packages/decap-cms-core/src/components/App/NotFoundPage.js` | ソース | 404ページコンポーネント |
| Collection.js | `packages/decap-cms-core/src/components/Collection/Collection.js` | ソース | コレクション画面コンポーネント |
| Editor.js | `packages/decap-cms-core/src/components/Editor/Editor.js` | ソース | エディタ画面コンポーネント |
| Workflow.js | `packages/decap-cms-core/src/components/Workflow/Workflow.js` | ソース | ワークフロー画面コンポーネント |
