πŸ—“οΈ 08062026 1400

STALETIME AND GCTIME

Two knobs control how tanstack_query manages cached data. Getting them right is the difference between "why does it keep refetching?" and "why is my data gone?"

What Each Controls​

  • staleTime β€” how long data is considered fresh after fetching. While fresh, no background refetch happens, even if a new component mounts requesting the same data
  • gcTime (garbage collection time) β€” how long unused cache entries stay in memory after their last subscriber unmounts. After this window, the entry is deleted entirely

They answer different questions:

  • staleTime: "Should I refetch this?"
  • gcTime: "Should I keep this in memory?"

Cache Entry Lifecycle​

fetch completes
β”‚
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” staleTime expires β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” last subscriber β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” gcTime expires β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ FRESH β”‚ ─────────────────► β”‚ STALE β”‚ unmounts β”‚ INACTIVE β”‚ ───────────────► β”‚ DELETED β”‚
β”‚ β”‚ β”‚ β”‚ ──────────────────► β”‚ β”‚ β”‚ β”‚
β”‚ no β”‚ β”‚ refetch β”‚ β”‚ no β”‚ β”‚ gone from β”‚
β”‚ refetch β”‚ β”‚ on next β”‚ β”‚ observersβ”‚ β”‚ memory β”‚
β”‚ β”‚ β”‚ trigger β”‚ β”‚ β”‚ β”‚ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  • Fresh β€” served from cache, no refetch triggered
  • Stale β€” still served from cache instantly, but a background refetch fires on the next trigger (mount, window focus, reconnect)
  • Inactive β€” no component is reading this data; cache entry lives on borrowed time (gcTime countdown starts)
  • Deleted β€” gone; next request starts from scratch with a loading state

Defaults and Why They Exist​

OptionDefaultRationale
staleTime0Data is stale immediately β€” guarantees freshness at the cost of more network requests
gcTime5 minutesKeeps recently viewed data around for quick back-navigation without being a memory leak

The staleTime: 0 default surprises people. It means every component mount triggers a background refetch β€” the UI still shows cached data instantly, but there's always a network request behind it. This is the stale-while-revalidate strategy in tanstack_query.

Common Configurations​

"Refetch freely" (default)​

staleTime: 0 // always refetch in background
gcTime: 5 * 60_000 // keep unused data 5 min

Good for: real-time dashboards, frequently changing data.

"Cache for a bit"​

staleTime: 60_000 // fresh for 1 minute
gcTime: 5 * 60_000 // keep unused data 5 min

Good for: most CRUD apps. Reduces network chatter without showing stale data for long.

"Rarely changes"​

staleTime: Infinity // never auto-refetch
gcTime: Infinity // never garbage collect

Good for: reference data (country lists, enum mappings, feature flags). Only refreshes on explicit invalidation.

How They Interact​

  • staleTime > gcTime is pointless β€” data gets deleted before it would have been refetched
  • gcTime: 0 means data is deleted the instant a component unmounts β€” navigating back always shows a loading spinner
  • staleTime: Infinity still respects manual invalidateQueries() β€” the data becomes stale on demand
WARNING

SSR gotcha: don't set gcTime below 2000 (2 seconds) in SSR apps. The server-rendered data needs to survive long enough for the client to hydrate and reference it. See tanstack_query_ssr_hydration for the full pattern.

Setting Defaults​

Configure once in QueryClient, override per-query when needed:

const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 60_000,
gcTime: 5 * 60_000,
},
},
})

Per-query override (see tanstack_query_cheatsheet for full API):

useQuery({
queryKey: ['countries'],
queryFn: fetchCountries,
staleTime: Infinity, // overrides default
})

References​