· 2 min read
A Practical Prefetching Strategy with TanStack Query and Router
A dashboard that feels slow loses trust. Users click a navigation link and expect the data to already be there. I built a prefetching strategy that makes a dashboard feel instant, even when the API calls take hundreds of milliseconds.
The strategy has three layers: intent-based preloading on hover, route-level prefetching in loaders, and pagination prefetching for tables.
Intent-Based Preloading
TanStack Router supports defaultPreload: 'intent', which starts loading a route’s data when the user hovers over a link. I enabled this globally. The effect is that by the time the user clicks, the data is already in the TanStack Query cache and the page renders immediately.
This single configuration change made the biggest perceived performance improvement of anything I did.
Route-Level Prefetching
I moved from beforeLoad to loader-based prefetching on every route. The loader function calls queryClient.ensureQueryData() with the same query options the page components use. This means the data fetching starts at navigation time, not at component mount time.
The key decision was to use the same query options factory functions in both the loader and the components. I extract these into domain-specific files so the loader and the component always agree on cache keys and stale times.
Pagination Prefetching
For paginated tables, I prefetch the next and previous pages when the current page loads. If hasNextPage is true, a background queryClient.prefetchQuery() fires for page N+1. Same for the previous page.
The user sees a loading spinner on the first page load, but every subsequent page navigation is instant because the data was prefetched. Combined with the row intersection observer that tracks which rows are visible, I can even prefetch detail views for rows the user is likely to click.
What I Disabled
I turned off refetchOnWindowFocus globally. Stale times are tuned based on how frequently each type of data changes. This eliminated the jarring full-page refresh that users experienced when switching back to the dashboard tab.