定義
サーバー状態キャッシュライブラリ(TanStack Query など)で「今書いたいいねやコメントをすぐ画面に出したい」とき、人はよく staleTime を下げようとする。だがそれは間違ったつまみだ。staleTime はデータを何秒間フレッシュとして信頼し、バックグラウンド refetch を控えるかを決める つまみであり、invalidateQueries はその staleTime を無視して該当クエリを今すぐ refetch させる。 即時反映の仕組みは staleTime ではなく invalidate だ。
ここで stale は「古くなった」と印を付けられた状態、refetch はサーバーからデータを取り直すこと、invalidate はキャッシュに「古い」と印を押して取り直させる行為を指す。
なぜ必要か
症状シナリオ: ユーザーがいいねを押すのに、画面がすぐ変わらない。「じゃあ staleTime を 0 にしよう」と考えると、今度はウィンドウフォーカスやコンポーネントマウントのたびに refetch が飛び、サーバートラフィックが爆発する。鮮度とトラフィックが対立するように見える。
正解は二つのつまみの役割を分けることだ。staleTime は鮮度 SLA(例:「10 秒以内に反映」)として保ちバックグラウンドトラフィックを抑え、書き込みミューテーションが成功したら該当クエリだけを invalidate してその瞬間だけ refetch を強制する。すると普段はトラフィックが静かで、自分の書き込み直後には即時更新される。
動作原理
| つまみ | 制御する対象 | 即時反映に使う |
|---|---|---|
staleTime | バックグラウンド/フォーカス/マウント refetch の有無 | いいえ |
invalidateQueries | 今すぐ refetch(active)+ stale 印(inactive) | はい |
- active(マウント中)クエリを invalidate すると staleTime に関係なくすぐ refetch する。
- inactive クエリは stale 印だけ付き、次の訪問時に refetch する。
- invalidate に渡す queryKey プレフィックスで影響範囲を絞る(scoped invalidation)— 無関係なクエリまで refetch しないようにする。
実務での適用
体感の即時性が重要なら、onMutate の楽観的パッチで先に変え、onSettled で invalidate によりサーバーの真実と合わせる。
トレードオフ
- staleTime を下げて即時性を確保: 実装は簡単だが、あらゆる refetch ポイントでトラフィックが増える。規模が大きいとサーバーがつらい。
- staleTime を保ち書き込み後に invalidate: コードは少し増えるが、トラフィックと即時性を同時に満たす。scoped invalidation で refetch 範囲も制御できる。
- ダウンロードのバイトまで節約するなら条件付きリクエスト(ETag/304)と併用する — invalidate で refetch が走っても、内容が変わっていなければ 304 で本文を受け取らない。
使ってはいけない場合
- リアルタイム協業のように他人の変更が秒単位で流れ込む画面 — invalidate のポーリングより購読(WebSocket/Realtime)が合う。
- 書き込みの即時性が要らない純粋な読み取りダッシュボード — staleTime だけで足りるなら invalidate を乱用しない。
よくあるミス
- 即時反映されないからと staleTime を 0 にしてトラフィックを爆発させる。
- invalidate のスコープが広すぎる(全体
invalidateQueries())て無関係なクエリまで refetch する。 - 楽観的パッチなしで invalidate だけを使い、書き込み→refetch の間にちらつきが見える。
- onError のロールバックを省いて、失敗した楽観的状態が画面に残る。
関連概念
- ssr-prefetch-query-cache-hydration — サーバーでクエリキャッシュを埋めて渡す hydration
- optimistic-outbox-rebase — オフライン/楽観的書き込みをキューで管理し収束させる
- race-safe-async-ui-requests — 楽観的ロールバックとリクエスト競合の処理