Next.js14(AppRouter)における、Server ComponentsとClient Componentsの使い分け+データフェッチについて、学習目的で自分なりにまとめてみました。
間違いがありましたらご指摘いただけると助かります。
情報源は以下です。
⭐️Server Component
- 基本的には、Server Componentを使用する。(デフォルト)
- データフェッチが高速になる。(サーバーがAPIやデータセンターと物理的に近い位置になるから。)
- クライアント側へのJavaScriptバンドルのサイズを減らして初期表示を早くするため。
- SEOの向上
- 完全なHTMLの状態でクライアントに表示される。
- 初期表示が早くなる。
- 動的メタデータ(ogpなど)が設定可能。
- クライアントのスペックに依存しなくなる。
- セキュリティの強化
- サーバー側で実行される=クライアント側で漏洩しなくなる。
Server Componentでのデータフェッチ
- データフェッチは基本的にServer Componentで行う。(Client Componentでは可能な限り行わない。)
- 初回ページ読み込み速度の向上
- SEOの向上
- セキュア
- キャッシュが利用できる
- データフェッチは可能な限り末端コンポーネント(リーフ)で行い、Propsのバケツリレーをなくす。
- トレードオフとして重複リクエストが発生しやすくなるため、Request Memoizationを利用する。(データフェッチ層に分離することで重複を保証させる)
- N+1問題に気をつける
- DataLoaderを使用して情報を一括取得し回避
https://dummyjson.com/users/?id=1&id=2&id=3…..
- Eager Loadingパターンで回避
- N+1の最初の1回のリスエストで関連する必要な情報を全て取得する。
- DataLoaderを使用して情報を一括取得し回避
- 並行データフェッチング(コンポーネントを兄弟分割 or
Promise.all()
)を可能な限り行う。- preloadパターン
- 親子コンポーネントそれぞれで別のフェッチをしてる時は、ウォーターフォールになるため、子でのフェッチをあえて親側でも行うことで、親コンポーネントレンダリング時に並行フェッチすることができる。(子でのフェッチ時はRequest Memoizationが機能する。)
- 例)親→fetch A, fetchBを並列で実行 子→fetchB(Request Memoizationにより重複リクエスト排除)
- 親子コンポーネントそれぞれで別のフェッチをしてる時は、ウォーターフォールになるため、子でのフェッチをあえて親側でも行うことで、親コンポーネントレンダリング時に並行フェッチすることができる。(子でのフェッチ時はRequest Memoizationが機能する。)
- preloadパターン
- ユーザー操作に基づくデータフェッチと再レンダリング
- Server ActionsとuseActionState(Client Components)を利用する。
- router.replaceなどによってURLを更新しデータを取得する。
- ストリーミングを使用する。
loading.tsx
Suspence
- TTFBの短縮とLayoutShiftのデメリットのバランスを考える。
- CumulativeLayout Shift(CLS)に注意。
- 外部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のメリットが発揮できない。
- ユーザー操作に基づいたデータフェッチに弱い。
- Server ActionsとuseActionState(Client Components)を利用する必要がある。
- router.replaceなどによってURLを更新しデータを取得する。(画面の一部のみに検索結果を出したい場合などに向いていない。)
⭐️Client Component
- 可能な限り使わない。
- Server Componentsを中心に設計し、必要に応じてClient Componentsを末端に配置したり、Compositionパターンで組み込んで実装を進めていくとよい。
- ユースケース
- Hooksを使用するとき(
useState
,useEffect
等) - イベントハンドラを利用するとき(
onClick
,onChange
等) - ブラウザAPIを利用するとき(
localStorage
,sessionStorage
,window
,document
等) - Client Componentsを提供するサードパーティコンポーネントを利用するとき
- RSC Payload転送量を減らしたいとき(重いCSSなどがある場合)
- クライアント操作が多い
- フォーム
- 検索
- タブ切り替え、ハンバーガーメニュー等
- Hooksを使用するとき(
- 子コンポーネントは自動的にClient Componentになる。
- Client Componentは、Componentツリーの末端で使うと良い。
- Compositionパターンを使えば回避できる。(Client ComponentのChildrenにServerComponentを渡す)
Client Componentでのデータフェッチ
- 基本的には非推奨。できる限りServer Componentでデータフェッチをする。
- パブリックなネットワークに公開されてしまうため。
- JavaScriptのバンドルファイルが増加するため。
- 実装コストが高い
- サードパーティライブラリとNext.jsのキャッシュのrevalidate機能との統合が難しい。
- サードパーティライブラリの学習コストが高い
- API側のセキュリティ対策が必要
- Client Componentでデータフェッチをする場合は、サードパーティライブラリを使用。(
useEffect
内でのfetch()
はキャッシュの設定が難しいため、非推奨)useSWR()
- TanStack Query
- Server Componentsでのデータフェッチ時と同様、並行データフェッチング(コンポーネント分割 or
Promise.all()
)を可能な限り行う。
まとめ
以上です。
間違っている箇所等ありましたらご指摘いただけますと幸いです。