🧭
Next.js App Routerの状態管理 判断ガイド
本記事はCommune Developers Advent Calendar 2025の13日目の記事です。
はじめに
この記事は、筆者がチームで「この状態を何で扱うか」を判断するにあたりガイドラインを整理したものです。
このガイドラインの目的は以下の通りです:
- 一定の品質を担保できる判断基準を整備する
- 「状態を増やしすぎない」ための指針を明確にする
ただし、ここで紹介する選択肢がすべてではありません。また、ここで示す判断フローは一例であり、プロジェクトの規模や要件によって最適解は異なります。チームで議論する際の参考としてご活用ください。
この記事の前提
- Next.js App Router(Server Components/ Server Function)を利用した環境
- Zustand、nuqs、react-hook-formを使用
出てくる選択肢ごとの役割
以降で状態をどの選択肢で管理するのが良いのかを判断します。その上で、各選択肢で「何ができるか」を先にざっくり把握しておくと、後の判断フローが読みやすくなるので簡単にまとめておきます。
-
Server Components
- サーバーで取得したデータを、サーバーコンポーネントで描画する
-
Server Function(Server Actions) +
useActionState- 書き込み(フォーム送信/更新/削除)と、そのローディング/成功/失敗の取り扱いを行う
-
nuqs
- URL(クエリ)と状態を同期する
-
Zustand
- アプリ内で、複数コンポーネントが参照する状態を共有する
- Context APIより再レンダリング制御がしやすく、ボイラープレートも少ない
-
react-hook-form
- フォーム周りの状態(入力/バリデーションなど)を扱う
-
useState- コンポーネント内で完結する局所状態を持つ
比較表
ここでは、汎用的なクライアント状態管理(UI状態や共有状態など)に絞って比較します。
非同期通信には useActionState、フォームには react-hook-form を使う前提です。
| 項目 | コンポーネント状態(useState) | Zustand | nuqs |
|---|---|---|---|
| スコープ | 単一コンポーネント | グローバル(アプリ全体)/ 複数ページで共有 | URL(ブラウザ履歴と同期) |
| 永続性 | なし(再マウントで消える) | ミドルウェアでストレージ永続化が可能 | URLに保持(共有・復元が可能) |
| 共有しやすさ | props経由のみ | 全コンポーネントで即共有 | URL経由で共有 |
| ブラウザ履歴 | 影響なし | 影響なし | 履歴にpushすることも可能 |
| SSR/SSG対応 | ○ | ○(ハイドレーション時の注意が必要) | ◎(URLから直接復元) |
判断フロー
原則:状態を作る前のチェック
判断フローに入る前に、以下を確認してください:
- それは本当に「状態」として持つ必要があるか?
- コンポーネント分割/コンポジションで局所化できないか
- propsで渡すだけで十分ではないか(過剰な共有を避ける)
- 非同期通信(ミューテーション)はまず Server Function +
useActionStateで完結できないか - フォームに関する状態は
react-hook-formに寄せる
フローチャート
まず、扱いたいデータが以下のどちらかを判断します:
- サーバーが管理するデータ:DB等の永続データ(source of truth)
- クライアント状態:UIや操作の都合で必要な状態(開閉、選択、入力途中など)
開始
│
├─ それは「サーバーが管理するデータ」か?(DB等の永続データか)
│ │
│ ├─ はい
│ │ → ✅ 読み取り:サーバーで取得してサーバーコンポーネントで描画
│ │ → ✅ 書き込み:Server Function + useActionState
│ │
│ └─ いいえ(UI/操作の都合で必要な“クライアント状態”)
│ │
│ ├─ 状態として管理する必要がある?
│ │ │
│ │ ├─ コンポーネント分割で状態を消せる?
│ │ │ (状態を使う場所の近くに寄せる)
│ │ │ → ✅ コンポーネントを分割し、コンポジションで組み立てる
│ │ │
│ │ ├─ フォームの取り扱い
│ │ │ → ✅ react-hook-form
│ │ │
│ │ └─ はい(状態管理が必要)
│ │ │
│ │ └─ ライフサイクル変化と同期したい?
│ │ (例:ブラウザの戻る/進む、URL同期)
│ │ │
│ │ ├─ はい → ✅ nuqs
│ │ │
│ │ └─ いいえ
│ │ │
│ │ └─ 複数コンポーネントから広く参照したい?
│ │ │
│ │ ├─ はい → ✅ Zustand
│ │ │
│ │ └─ いいえ → ✅ コンポーネント状態(useState)
│
│
⚠️ 注意:このフローチャートは筆者の環境における一般的な指針です。すべてのケースが
明確に分類できるわけではありません。迷ったらチームで相談してください。
判断するときのチェック項目
- Zustandに、サーバーから取得したデータをキャッシュ目的で載せない
- nuqsに、機密情報・大きなデータ・高頻度で変わるデータを載せない
- Server Function +
useActionStateで十分な箇所に、先にZustandを持ち込まない - それは「サーバーが管理するデータ」か?それとも「クライアント状態」か?
- 共有範囲はどこか?(どの画面/どのコンポーネントまで)
- 破棄/初期化のタイミングはいつか?(遷移時、ログアウト時など)
- URLに載せる必然はあるか?(共有/履歴/復元が本当に必要か)
まとめ
この記事では、Next.js App Router環境における状態管理の判断基準を整理しました。
ポイントは以下の通りです:
- まず「サーバーが管理するデータ」か「クライアント状態」かを判断する
- クライアント状態は、スコープ(局所/共有)とライフサイクル(URL同期の有無)で選択肢を決める
- 状態を増やす前に、コンポーネント分割やpropsで解決できないか検討する
迷ったらチームで相談し、プロジェクトに合った判断基準を育てていきましょう。
参考リンク
GitHubで編集を提案
Discussion