Open6

Next.js App Routerらしいコンポーネント最適解を見つける

gizumongizumon

Next.js App Routerで利用される状態管理ライブラリ


https://npmtrends.com/jotai-vs-recoil-vs-redux-vs-valtio-vs-zustand

  • jotai
    • 特徴
      • Atomベースの状態管理ライブラリ
  • nrstate
    • 特徴
      • Server ComponentとClient Componentでグローバルにステータスを共有できるライブラリ
    • メリット/デメリット
      • メリット: サーバーコンポーネントでステータスを持てる
      • デメリット: スター&メンテ頻度が低い
    • コメント:
      • 最初良いと思ったけど、スターの低さから色々検討してみた結果、サーバー側はステートレスに作る思想からズレる→キャッシュなどに影響が出るため、極力ステータスを持たないで作るべきと納得。利用は見送り。
  • zustand
    • 特徴
      • Stateベースの状態管理ライブラリ
gizumongizumon

グローバルステート系ライブラリの種類

Storeベース (トップダウン)

状態をStoreという一つのオブジェクトとして管理し、各コンポーネントから参照できる

  • Redux
  • zustand

Atomベース (ボトムアップ)

Storeベースとは異なり複数のAtomで状態管理を行い、各コンポーネントから複数のAtomを参照できる

  • Recoil
  • jotai

Proxyベース

プロキシは通常の JavaScript オブジェクトでありどこからでも変更可能

  • Valtio

出典: https://react-uncle-blog.netlify.app/blog/react-state-managment
REF: https://zenn.dev/jotaifriends/articles/d714f9c16c1d3a?redirected=1

gizumongizumon

zustand vs jotai

zustand

  • Star: 41K, Size: 1.2KB, Weekly download: 3.1M
  • Storeベース (トップダウン) ※e.g. Redux
    • 単一のストアが基本 (複数のストアを作ることもできる)
    • Reactの外部でステートが管理される
  • Storeが親でコンポーネントが子
  • 単一方向のデータフロー

jotai

  • Star: 16K, Size: 3.3KB, Weekly download: 861K
  • Atomベース (ボトムアップ) ※e.g. Recoil
    • 複数のストアを作れる
    • Reactの内部でステートが管理される (useState + useContext)
  • コンポーネントが親でAtomが子のような依存関係になる(?)
  • 双方のデータフロー
gizumongizumon

App routerらしい最適解を見つける

課題1: 状態について

App routerでServer ComponentとClient Componentが分かれており、
単純にPages routerからApp routerへ移行しようとした際に、ステートの管理方法などを再検討しなければならず、
一旦、全てClient Componentで作るみたいなことが発生する。

最初に躓くServer Componentに移行する際に躓く主なポイントはこのステートの管理だと思う。

解決作成としては、主に下記の3点。

  1. クエリパラメータ/Cookieなどサーバー側で状態を管理できる方法利用する
  • クエリパラメータ: ユーザー視点でどの状態のページでアクセスさせたいかを考え、ユーザーに取って有益であればクエリパラメータにデータを保管するのは有益に思える。一方で、例えばにヘッダーの開閉状態などユーザーに取って、無益なページのステータスはクエリパラメータに持たすべきではない。クエリパラメータが増えるということは、UXが悪くなるのは当然のこと、キャッシュの分散等にもつながるため注意。
  • Cokkieを利用する: Cookieを読み出す場合、Next.jsはキャッシュを利用しなくなるため、パフォーマンス悪化につながるため、基本的には使わない方が良いと思う
  1. Composition Componentを利用する
  • 基本的にClient ComponentからServer Componentを読み出すことは不可だが、Server Componentに読み込んだClient ComponentのchildrenにServer Componentを渡すことは可能。(Hydrationの仕組み)
  • これを利用してClient Componentに逃がすComponentを最小化できる
  1. グローバルステートを利用する
  • 基本的にグローバルステートを利用しない場合、StateのバケツリレーがClient Component -> Server Component間で途切れてしまい躓く。
  • UIレイアウトとデータ層のコンポーネント設計を共存させるためにはグローバルステートによりコンポーネントのツリー外でステートを管理してあげる必要がある

課題2: Render as you fetchの最適解について

  • fetchさせる場所について
    • ページ全体で利用するpropsは、pagesでawait抜きでコールして、Promiseをpropsとして渡してあげるのが良さそう
  • generateMetadataについて
    • generateMetadata内でasync/await でメタデータを生成している場合、ページの初期レンダリングのボトルネックとなる。(generateMetadata -> page コンポーネントのレンダリングみたいな感じで同期的に処理される挙動に見える)
    • このベストプラクティスは現状なし。