All Docs
FeaturesDepositClearUpdated March 12, 2026

Reducing Unnecessary Network Requests with React Query staleTime

Reducing Unnecessary Network Requests with React Query staleTime

Release: v0.1.237 · Category: Performance · Frontend
File affected: src/lib/trpc/provider.tsx

Background

The platform uses tRPC together with TanStack React Query to fetch data from the server. Every tRPC hook (useQuery) is backed by a React Query cache entry.

React Query's default staleTime is 0 — meaning cached data is considered stale immediately after it is fetched. Whenever a component that calls a query mounts (or re-mounts), React Query schedules a background refetch, even if the same data was fetched milliseconds ago by a sibling component.

On dashboard pages that mount many components in parallel — each calling their own tRPC hooks — this default behaviour generates a burst of redundant HTTP requests on every navigation.

What Changed

A global default staleTime of 30,000 ms (30 seconds) is now set on the shared QueryClient:

// src/lib/trpc/provider.tsx
new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 30_000,
    },
  },
})

This means that if a query's data was fetched within the last 30 seconds, React Query will serve it from the cache without issuing a new network request — regardless of how many components mount and call the same query.

Choosing the Right staleTime Per Query

The 30-second default is appropriate for most application data. However, some data is effectively static between deployments and can be cached indefinitely:

QueryRecommended staleTimeReason
billing.getPlansInfinityPlan catalogue changes only on deployment
wearTear.listInfinityReference data; curated, not user-driven
complianceHelp.listInfinityCompliance rules are deployment-time constants
User session / tenancy data30_000 (default)Changes during a session; use the global default
Inspection evidence / photos0 or short TTLFrequently mutated; keep fresh

To apply a per-query override:

// Treat plan data as permanently fresh until the page is reloaded
const { data: plans } = trpc.billing.getPlans.useQuery(undefined, {
  staleTime: Infinity,
});

Why This Matters

  • Fewer network round-trips. Dashboard pages with 10+ concurrent queries previously fired up to 10 background requests on every mount. With a 30-second window, subsequent mounts within that window are served from cache.
  • Lower server load. Read-heavy reference data endpoints no longer receive redundant hits from UI re-renders or navigation back to a previously visited page.
  • Predictable behaviour. Developers can now reason about cache lifetime explicitly rather than relying on the opaque "always refetch" default.

Trade-offs and Considerations

  • Staleness window. Data mutated by another session (e.g. a landlord updating a category on a separate device) will not be reflected until the cache entry expires (30 seconds) or is explicitly invalidated via queryClient.invalidateQueries.
  • Mutation-driven invalidation. Any tRPC mutation that changes data should continue to call utils.<procedure>.invalidate() after success. The staleTime change does not affect invalidation — invalidated queries are always refetched regardless of staleness.
  • Infinity staleTime queries. Queries set to staleTime: Infinity will never refetch in the background. Only use this for data that is guaranteed not to change within a user session (e.g. static reference lists).

Further Reading