ウェブエンジニア問題集

状態管理の設計 — サーバーとクライアントで何を持つか

学習者学習者

状態って全部フロントで持てばよくない?わざわざサーバーと切り分ける意味があるの?

なぜ「どこに持つか」を考える必要があるのか

アプリケーションには様々な「状態」が存在する。ユーザーの入力中のフォームデータ、APIから取得した一覧データ、ログイン中のユーザー情報、UIの開閉状態など。

これらを「とりあえず全部フロントで持つ」「とりあえず全部サーバーに問い合わせる」とすると、どちらの場合も問題が起きる。状態の置き場所を設計段階で決めておくことが、保守性とパフォーマンスの両方に影響する。


状態の分類

状態はその性質によって、適切な管理場所が異なる。

サーバー状態(Server State)

データの正(Single Source of Truth)がサーバー側にあるもの。DBに保存されている情報が該当する。

  • ユーザー情報、商品一覧、注文履歴など
  • 他のユーザーの操作によって変わりうるデータ
  • フロントはあくまで「取得して表示する」立場

フロント側ではキャッシュとして一時的に保持するが、正のデータはサーバーにある。React QueryやSWRのようなライブラリはこのサーバー状態の取得・キャッシュ・再取得を管理するためのものである。

クライアント状態(Client State)

フロントエンド側でのみ意味を持つ状態。サーバーに保存する必要がないもの。

  • モーダルの開閉、アコーディオンの展開状態
  • フォームの入力途中の値
  • 現在選択中のタブ、フィルター条件
  • ダークモードのON/OFF

これらはReactであれば useStateuseReducer、あるいはグローバルな状態管理ライブラリ(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に反映すべき状態はどれか

これらを事前に決めておくことで、実装時に「この値はどこに持つべきか」で迷うことが減り、コードの一貫性が保たれる。