Stale-While-Revalidate
Show cached (possibly stale) data immediately, then revalidate in the background and update the UI when fresh data arrives.
The problem I keep seeing
Users navigate away and back, or switch tabs. If every visit waits for a fresh network response, the UI feels slow and flickers (empty → content). You want to show something immediately—ideally the last data you had—and then refresh in the background so the screen updates when new data is ready.
Naive approach
No cache: every mount triggers a fetch, and the UI shows loading until it completes. Revisits and tab switches always start from scratch.
First improvement
Keep a simple cache (e.g. in a ref or module). On mount, if cache has data for this key, render it immediately and still fire a fetch; when the fetch completes, update cache and state. User sees stale data first, then fresh data without a loading block.
Remaining issues
- Cache invalidation: When to evict or refetch (e.g. after a mutation, or after a TTL). A library gives you
staleTimeand invalidation. - Multiple consumers: Several components may need the same query; a single cache (e.g. React Query) avoids duplicate requests and keeps them in sync.
- Param changes: When the key (e.g. userId) changes, you may want to show the previous result as placeholder until the new one loads (no flash of empty).
Production pattern
Use React Query (or SWR). Set staleTime so data is considered “fresh” for a period and won’t refetch on remount; set gcTime so unused cache entries are kept for back navigation. You get SWR automatically: show cached data, refetch in background when stale. Optionally use placeholderData: (previous) => previous when the query key changes so the previous result stays visible until the new one loads.
When I use this
- Most GET data: Profile, settings, list views. Users expect instant display when they’ve seen it before; background refresh keeps it current.
- Dashboard / metrics: Show last known values, refetch on interval or focus.
- Skip when: Data must never be stale (e.g. critical financial step); then block UI until fresh data or show a clear “out of date” state.
Gotchas
- staleTime vs gcTime:
staleTime= “don’t refetch while fresh”;gcTime= “how long to keep unused data in cache.” Both affect when you see cached vs loading. - Refetch on focus: React Query refetches when the window regains focus by default. Turn off with
refetchOnWindowFocus: falseif you don’t want that. - Placeholder vs initialData:
placeholderDatais not cached and is used only for display;initialDatais written into the cache and can prevent the first fetch if you’re not careful.