結論:アプリの特性に応じて、クライアントコンポーネントとサーバーコンポーネントを適切に使い分けるのがベスト、という話です。
何でもかんでもサーバーコンポーネントにすれば良いってもんじゃない。とは言っても逆もまた然りで、何でもかんでも脳死で "use client" を付けてクライアントコンポーネントにしてしまうのも良くありません。
Next.jsを実務で触っていく中で、「サーバーコンポーネント」という概念を知りました。
要は、サーバー側でHTMLを生成してクライアントに送る仕組みです。APIからのデータフェッチもサーバー側で行えるというのが特徴です。
「へ〜、useEffect はクライアントでレンダリング後に実行されるけど、サーバーでデータを先にフェッチできれば実行タイミングも早くていいじゃん。しかも物理的にDBにも近いし、バンドルサイズも減るからパフォーマンス絶対上がるでしょ!」
…なんて思って試行錯誤してみたのですが、結論としては、**「部分的にサーバーコンポーネントを採用」**という形に落ち着きました。
サーバーコンポーネントには、たとえばローカルストレージのデータを読めなかったり、useState や useEffect のようなフックが使えなかったりといった制約があります。でも、それ自体は大きな問題ではありませんでした。
一番の問題は、サーバーコンポーネントでは Zustand のような状態管理ライブラリに保存されたデータを参照できないという点です。
私が作っているアプリでは、画面の初回表示時にデータをフェッチして、それを Zustand の store に保存します。
以後はその store に保存されたデータを参照し、必要に応じて DB 更新とともに store も更新する設計です。
つまり、「store にデータがあればそれを使い、なければフェッチする」という構成です。
ところがサーバーコンポーネントでは、クライアント側にある store の中身を確認することができません。
つまり、「store にデータがあるかどうかを判定できない」→「毎回フェッチするしかない」という状況になってしまいます。
これでは、せっかく store に保存している意味がありません。
たしかにサーバーコンポーネントを使えば初回表示は高速になります。
でも、クライアントコンポーネントにして初回表示が多少遅れても、その後の画面遷移や操作時にフェッチ不要で store のデータを使えた方が、トータルのユーザー体験は高いと感じました。
サーバーコンポーネントの場合
画面A(fetch)
↓
画面B(fetch)
↓
画面A(fetch)
クライアントコンポーネントの場合
画面A(fetch)
↓
画面B(fetch)
↓
画面A(store のデータを利用)
ただし、これはあくまでインタラクティブ性の高いアプリケーションの話です。
サーバーコンポーネントが悪いというわけではありません。
Next.js には、フェッチしたデータをキャッシュできる機能もあります。
たとえば「初回フェッチ後は1時間そのデータを使い回す」といったことも可能です。
データの更新頻度が低いアプリや静的な企業サイトのような構成であれば、状態管理ライブラリを使わなくても十分対応できますし、サーバーコンポーネントで初回表示も速く、キャッシュも効いて、とても効率的です。
結論:アプリの特性に応じて、クライアントコンポーネントとサーバーコンポーネントを適切に使い分けるのがベスト、という話です。