# ページ遷移とレンダリングのデータフロー このドキュメントは、GROWIのページ遷移からレンダリングまでのデータフローを解説します。 ## 登場人物 1. **`[[...path]].page.tsx`**: Next.js の動的ルーティングを担うメインコンポーネント。サーバーサイドとクライアントサイドの両方で動作します。 2. **`useSameRouteNavigation.ts`**: クライアントサイドでのパス変更を検知し、データ取得を**トリガー**するフック。 3. **`useFetchCurrentPage.ts`**: データ取得と関連する Jotai atom の更新を一元管理するフック。データ取得が本当に必要かどうかの最終判断も担います。 4. **`useShallowRouting.ts`**: サーバーサイドで正規化されたパスとブラウザのURLを同期させるフック。 5. **`server-side-props.ts`**: サーバーサイドレンダリング(SSR)時にページデータを取得し、`props` としてページコンポーネントに渡します。 --- ## フロー1: サーバーサイドレンダリング(初回アクセス時) ユーザーがURLに直接アクセスするか、ページをリロードした際のフローです。 1. **リクエスト受信**: サーバーがユーザーからのリクエスト(例: `/user/username/memo`)を受け取ります。 2. **`getServerSideProps` の実行**: - `server-side-props.ts` の `getServerSidePropsForInitial` が実行されます。 - `retrievePageData` が呼び出され、パスの正規化(例: `/user/username` → `/user/username/`)が行われ、APIからページデータを取得します。 - 取得したデータと、正規化後のパス (`currentPathname`) を `props` として `[[...path]].page.tsx` に渡します。 3. **コンポーネントのレンダリングとJotai Atomの初期化**: - `[[...path]].page.tsx` は `props` を受け取り、そのデータで `currentPageDataAtom` などのJotai Atomを初期化します。 - `PageView` などのコンポーネントがサーバーサイドでレンダリングされます。 4. **クライアントサイドでのハイドレーションとURL正規化**: - レンダリングされたHTMLがブラウザに送信され、Reactがハイドレーションを行います。 - **`useShallowRouting`** が実行され、ブラウザのURL (`/user/username/memo`) と `props.currentPathname` (`/user/username/memo/`) を比較します。 - 差異がある場合、`router.replace` を `shallow: true` で実行し、ブラウザのURLをサーバーが認識している正規化後のパスに静かに更新します。 --- ## フロー2: クライアントサイドナビゲーション(`` クリック時) アプリケーション内でページ間を移動する際のフローです。 1. **ナビゲーション開始**: - ユーザーが `` をクリックします。 - Next.js の `useRouter` がURLの変更を検出し、`[[...path]].page.tsx` が再評価されます。 2. **`useSameRouteNavigation` によるトリガー**: - このフックの `useEffect` が `router.asPath` の変更 (`/new/page`) を検知します。 - **`fetchCurrentPage({ path: '/new/page' })`** を呼び出します。このフックは常にデータ取得を試みます。 3. **`useFetchCurrentPage` によるデータ取得の判断と実行**: - `fetchCurrentPage` 関数が実行されます。 - **3a. パスの前処理**: - まず、引数で渡された `path` をデコードします(例: `encoded%2Fpath` → `encoded/path`)。 - 次に、パスがパーマリンク形式(例: `/65d4e0a0f7b7b2e5a8652e86`)かどうかを判定します。 - **3b. 重複取得の防止(ガード節)**: - 前処理したパスや、パーマリンクから抽出したページIDが、現在Jotaiで管理されているページのパスやIDと同じでないかチェックします。 - 同じであれば、APIを叩かずに処理を中断し、現在のページデータを返します。 - **3c. 読み込み状態開始**: `pageLoadingAtom` を `true` に設定します。 - **3d. API通信**: `apiv3Get('/page', ...)` を実行してサーバーから新しいページデータを取得します。パラメータには、パス、ページID、リビジョンIDなどが含まれます。 4. **アトミックな状態更新**: - **API成功時**: - 関連する **すべてのatomを一度に更新** します (`currentPageDataAtom`, `currentPageEntityIdAtom`, `currentPageEmptyIdAtom`, `pageNotFoundAtom`, `pageLoadingAtom` など)。 - これにより、中間的な状態(`pageId`が`undefined`になるなど)が発生することなく、データが完全に揃った状態で一度だけ状態が更新されます。 - **APIエラー時 (例: 404 Not Found)**: - `pageErrorAtom` にエラーオブジェクトを設定します。 - `pageNotFoundAtom` を `true` に設定します。 - 最後に `pageLoadingAtom` を `false` に設定します。 5. **`PageView` の最終レンダリング**: - `currentPageDataAtom` の更新がトリガーとなり、`PageView` コンポーネントが新しいデータで再レンダリングされます。 6. **副作用の実行**: - `useSameRouteNavigation` 内で `fetchCurrentPage` が完了した後、`mutateEditingMarkdown` が呼び出され、エディタの状態が更新されます。