状態管理の設計 — サーバーとクライアントで何を持つか
学習者状態って全部フロントで持てばよくない?わざわざサーバーと切り分ける意味があるの?
なぜ「どこに持つか」を考える必要があるのか
アプリケーションには様々な「状態」が存在する。ユーザーの入力中のフォームデータ、APIから取得した一覧データ、ログイン中のユーザー情報、UIの開閉状態など。
これらを「とりあえず全部フロントで持つ」「とりあえず全部サーバーに問い合わせる」とすると、どちらの場合も問題が起きる。状態の置き場所を設計段階で決めておくことが、保守性とパフォーマンスの両方に影響する。
状態の分類
状態はその性質によって、適切な管理場所が異なる。
サーバー状態(Server State)
データの正(Single Source of Truth)がサーバー側にあるもの。DBに保存されている情報が該当する。
- ユーザー情報、商品一覧、注文履歴など
- 他のユーザーの操作によって変わりうるデータ
- フロントはあくまで「取得して表示する」立場
フロント側ではキャッシュとして一時的に保持するが、正のデータはサーバーにある。React QueryやSWRのようなライブラリはこのサーバー状態の取得・キャッシュ・再取得を管理するためのものである。
クライアント状態(Client State)
フロントエンド側でのみ意味を持つ状態。サーバーに保存する必要がないもの。
- モーダルの開閉、アコーディオンの展開状態
- フォームの入力途中の値
- 現在選択中のタブ、フィルター条件
- ダークモードのON/OFF
これらはReactであれば useState や useReducer、あるいはグローバルな状態管理ライブラリ(Zustand、Jotaiなど)で管理する。
両方にまたがる状態
一部の状態はどちらとも言い切れないケースがある。
- 下書き保存: 入力中はクライアント状態だが、一定間隔でサーバーに保存する
- ユーザー設定(言語、テーマ): サーバーにも保存するが、UIの即時反映のためにクライアントでも保持する
- ページネーションの現在ページ: UIの状態だが、URLに反映してサーバーへのリクエストパラメータにもなる
こうした状態は「どちらが正か」を明確に決めておく必要がある。たとえば下書きなら「最後にサーバーに保存された値が正」とし、クライアント側は未保存の変更を持つ一時的な状態として扱う。
データの加工はどの層でやるか
状態管理の設計では「どのデータをどこに持つか」だけでなく、「データの加工・整形をどの層が担うか」も重要になる。
たとえばAPIから取得した生データをそのままフロントの状態に入れることは少ない。日付のフォーマット変換、ネストされたオブジェクトのフラット化、不要なフィールドの除去など、何らかの加工を経てからUIで使える形になる。
この加工処理をフロントに置くと、フロントの状態管理層にAPIの仕様に関する知識が入り込む。APIの仕様変更があるたびにフロントを修正する必要が生じ、UIロジックとデータ整形ロジックが混在してコードの見通しが悪くなる。
BFF(Backend for Frontend)
BFFはフロントエンド専用の中間サーバー層である。バックエンドのAPIとフロントエンドの間に置き、フロントが必要とする形にデータを整形して返す。
役割は「データの加工・整形」に限らない。複数のAPIへのリクエストを1回にまとめる集約、フロントに不要なフィールドの除去、認証情報のサーバー側への隠蔽なども含む。
BFFを置くことで、フロントの状態管理層は「整形済みのデータを受け取って保持し、表示する」ことに専念できる。APIの仕様変更はBFF層で吸収されるため、フロント側のコードが安定する。
「どの層がどの状態を持ち、どこで加工するか」を設計段階で決めておくことで、各層の責務が明確になる。
キャッシュ戦略
サーバー状態をフロントで扱う際、毎回APIを叩くのは非効率である。一方で古いデータを表示し続けるのも問題になる。この「鮮度と効率のバランス」をキャッシュ戦略で設計する。
Stale-While-Revalidate
キャッシュされたデータをまず表示し、裏側で最新データを取得して置き換える手法。React QueryやSWRが採用しているアプローチで、ユーザーには待ち時間なくデータが表示され、最新データが届いたら自動的に画面が更新される。
キャッシュの無効化
データを更新(POST/PUT/DELETE)した後、関連するキャッシュを破棄して再取得する。たとえば商品情報を更新したら、商品一覧のキャッシュを無効化して最新の状態を取得し直す。
キャッシュの無効化を適切に設計しないと「操作したのに画面に反映されない」という問題が起きる。これはバグではなくキャッシュ設計の不備であることが多い。
設計時に決めておくべきこと
状態管理について設計段階で決めるべき事項をまとめる。
- 各データの正はどこか: サーバーかクライアントか、曖昧なものは明示的に定義する
- データ加工の責務はどこか: フロントで整形するか、BFF層で整形するか
- キャッシュ戦略: どのデータをどのくらいの期間キャッシュし、どのタイミングで無効化するか
- グローバル状態の範囲: アプリ全体で共有すべき状態は何か。安易にグローバルにしない
- URLと状態の関係: フィルター条件や現在ページなど、URLに反映すべき状態はどれか
これらを事前に決めておくことで、実装時に「この値はどこに持つべきか」で迷うことが減り、コードの一貫性が保たれる。