【Next.js14】App RouterにおけるServer/Client Componentsの使い分け＋データフェッチについてのまとめ。

Next.js14（AppRouter）における、Server ComponentsとClient Componentsの使い分け＋データフェッチについて、学習目的で自分なりにまとめてみました。

間違いがありましたらご指摘いただけると助かります。

情報源は以下です。

https://zenn.dev/akfm/books/nextjs-basic-principle

https://nextjs.org/docs

https://www.youtube.com/watch?v=Ca1h3KUfQ5k

# ⭐️Server Component

- 基本的には、Server Componentを使用する。（デフォルト）
    - データフェッチが高速になる。（サーバーがAPIやデータセンターと物理的に近い位置になるから。）
    - クライアント側へのJavaScriptバンドルのサイズを減らして初期表示を早くするため。
    - SEOの向上
        - 完全なHTMLの状態でクライアントに表示される。
        - 初期表示が早くなる。
        - 動的メタデータ（ogpなど）が設定可能。
    - クライアントのスペックに依存しなくなる。
    - セキュリティの強化
        - サーバー側で実行される＝クライアント側で漏洩しなくなる。

## Server Componentでのデータフェッチ

- データフェッチは基本的にServer Componentで行う。(Client Componentでは可能な限り行わない。）
    - 初回ページ読み込み速度の向上
    - SEOの向上
    - セキュア
    - キャッシュが利用できる
    <br>
- データフェッチは可能な限り末端コンポーネント（リーフ）で行い、Propsのバケツリレーをなくす。
    - トレードオフとして重複リクエストが発生しやすくなるため、Request Memoizationを利用する。(データフェッチ層に分離することで重複を保証させる)
    - N＋1問題に気をつける
        - DataLoaderを使用して情報を一括取得し回避
            - `https://dummyjson.com/users/?id=1&id=2&id=3…..`
        - Eager Loadingパターンで回避
            - N＋1の最初の1回のリスエストで関連する必要な情報を全て取得する。
            <br>
- 並行データフェッチング（コンポーネントを兄弟分割 or `Promise.all()`）を可能な限り行う。
    - preloadパターン
        - 親子コンポーネントそれぞれで別のフェッチをしてる時は、ウォーターフォールになるため、子でのフェッチをあえて親側でも行うことで、親コンポーネントレンダリング時に並行フェッチすることができる。(子でのフェッチ時はRequest Memoizationが機能する。)
            - 例）親→fetch A, fetchBを並列で実行　子→fetchB（Request Memoizationにより重複リクエスト排除）
            <br>
- ユーザー操作に基づくデータフェッチと再レンダリング
    - Server ActionsとuseActionState(Client Components)を利用する。
    - router.replaceなどによってURLを更新しデータを取得する。
    <br>
- ストリーミングを使用する。
    - `loading.tsx`
    - `Suspence`
    - TTFBの短縮とLayoutShiftのデメリットのバランスを考える。
    - [CumulativeLayout Shift](https://web.dev/articles/cls?hl=ja)(CLS)に注意。
    <br>
- 外部APIは細粒度なREST API（Chatty API）と相性が良い。
    - Server Componentsによってデータフェッチのコロケーションや分割が容易になり、コードやロジックの重複が発生しづらくなったため。
    - バックエンドチームには細粒度なREST APIが望ましいことを伝えるべき。

### fetch()を使用

- デフォルトはSSG（レスポンスがData Cacheにキャッシュされる）
- オプションでSSR / ISR / SSG に変更可能
- キャッシュ設定が簡単
- Request Memoization（同一リクエストの重複排除）を適用できる
- 同プロジェクトのAPIを呼び出す場合は、Route Handler（API）を作成する必要がある。
    - 通信回数が増えるため、APIを噛ませずにORMを直接使用した方が良い。
    - スマホアプリ等からもAPIを叩きたい場合やAPIを公開したい場合はあり。

### ORMを使用（Prismaなど）

- 基本的に`fetch()`を使用するより、ORMを使用してSever Componentsから直接DBをCRUDした方が通信回数が減る（APIを叩かなくて良い）ため良い。
- ORMを使用した場合のData Cacheの設定はデフォルトでSSR。変更したい場合は、RouteHandler（API）を介して`fetch()`で呼びだすか、`unstable_cache`を使用する。
- Request Memoization適用外。
    - 使用したい場合は、React Cacehを使用する。

### Server Componentでのデータフェッチにおけるデメリット

- リアルタイム性に弱い。（状態管理を使用した楽観的UI更新などができない。）
    - Reactアプリケーションでは、クライアントコンポーネントからフェッチ＋Reduxを更新して、画面を瞬時に更新する。みたいなことができたが、Server Componentでは難しい。
    - リアルタイム性を要する場面が多いアプリケーションではNext.jsのメリットが発揮できない。
    <br>
- ユーザー操作に基づいたデータフェッチに弱い。
    - Server ActionsとuseActionState(Client Components)を利用する必要がある。
    - router.replaceなどによってURLを更新しデータを取得する。（画面の一部のみに検索結果を出したい場合などに向いていない。）

# ⭐️Client Component

- 可能な限り使わない。
    - Server Componentsを中心に設計し、必要に応じてClient Componentsを末端に配置したり、Compositionパターンで組み込んで実装を進めていくとよい。
    <br>
- ユースケース
    - Hooksを使用するとき（`useState`, `useEffect`等）
    - イベントハンドラを利用するとき（`onClick`, `onChange`等）
    - ブラウザAPIを利用するとき（`localStorage`, `sessionStorage`, `window`, `document`等）
    - Client Componentsを提供するサードパーティコンポーネントを利用するとき
    - RSC Payload転送量を減らしたいとき（重いCSSなどがある場合）
    - クライアント操作が多い
        - フォーム
        - 検索
        - タブ切り替え、ハンバーガーメニュー等
        <br>
- 子コンポーネントは自動的にClient Componentになる。
    - Client Componentは、Componentツリーの末端で使うと良い。
    - Compositionパターンを使えば回避できる。（Client ComponentのChildrenにServerComponentを渡す）

## Client Componentでのデータフェッチ

- 基本的には非推奨。できる限りServer Componentでデータフェッチをする。
    - パブリックなネットワークに公開されてしまうため。
    - JavaScriptのバンドルファイルが増加するため。
    - 実装コストが高い
        - サードパーティライブラリとNext.jsのキャッシュのrevalidate機能との統合が難しい。
        - サードパーティライブラリの学習コストが高い
        - API側のセキュリティ対策が必要
        <br>
- Client Componentでデータフェッチをする場合は、サードパーティライブラリを使用。（`useEffect`内での`fetch()`はキャッシュの設定が難しいため、非推奨）
    - `useSWR()`
    - TanStack Query
    <br>
- Server Componentsでのデータフェッチ時と同様、並行データフェッチング（コンポーネント分割 or `Promise.all()`）を可能な限り行う。

# まとめ
以上です。
間違っている箇所等ありましたらご指摘いただけますと幸いです。