🧭

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で解決できないか検討する

迷ったらチームで相談し、プロジェクトに合った判断基準を育てていきましょう。

参考リンク

https://tech.commune.co.jp/entry/2025/10/28/171706
https://zenn.dev/akfm/articles/react-state-scope

GitHubで編集を提案
コミューン株式会社

Discussion