Documentation Index
Fetch the complete documentation index at: https://mintlify.com/TanStack/query/llms.txt
Use this file to discover all available pages before exploring further.
Prefetching allows you to fetch data before it’s actually needed, creating instant loading experiences for your users.
Basic Prefetching
Use queryClient.prefetchQuery() to prefetch data:
import { useQueryClient } from '@tanstack/react-query'
function Component() {
const queryClient = useQueryClient()
const prefetchPost = (id: number) => {
queryClient.prefetchQuery({
queryKey: ['post', id],
queryFn: () => fetchPost(id),
staleTime: 10 * 1000, // Only prefetch if data is older than 10 seconds
})
}
return (
<ul>
{posts.map((post) => (
<li key={post.id} onMouseEnter={() => prefetchPost(post.id)}>
<Link to={`/posts/${post.id}`}>{post.title}</Link>
</li>
))}
</ul>
)
}
When to Prefetch
On Hover
Prefetch when user hovers over a link:
function PostLink({ post }) {
const queryClient = useQueryClient()
return (
<a
href={`/posts/${post.id}`}
onMouseEnter={() => {
queryClient.prefetchQuery({
queryKey: ['post', post.id],
queryFn: () => fetchPost(post.id),
})
}}
>
{post.title}
</a>
)
}
On Focus
Prefetch when user focuses on an input:
function SearchInput() {
const queryClient = useQueryClient()
const [query, setQuery] = useState('')
return (
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
onFocus={() => {
// Prefetch popular searches
queryClient.prefetchQuery({
queryKey: ['popular-searches'],
queryFn: fetchPopularSearches,
})
}}
/>
)
}
On Route Change
Prefetch data for the next page in pagination:
function Posts() {
const queryClient = useQueryClient()
const [page, setPage] = useState(0)
const { data } = useQuery({
queryKey: ['posts', page],
queryFn: () => fetchPosts(page),
})
// Prefetch next page
useEffect(() => {
if (data?.hasMore) {
queryClient.prefetchQuery({
queryKey: ['posts', page + 1],
queryFn: () => fetchPosts(page + 1),
})
}
}, [page, data, queryClient])
return (
<div>
{/* Render posts */}
<button onClick={() => setPage(page + 1)}>Next</button>
</div>
)
}
On Mount
Prefetch related data when component mounts:
function PostDetail({ postId }) {
const queryClient = useQueryClient()
const { data: post } = useQuery({
queryKey: ['post', postId],
queryFn: () => fetchPost(postId),
})
// Prefetch related posts on mount
useEffect(() => {
if (post?.relatedIds) {
post.relatedIds.forEach((id) => {
queryClient.prefetchQuery({
queryKey: ['post', id],
queryFn: () => fetchPost(id),
})
})
}
}, [post, queryClient])
return <div>{/* Render post */}</div>
}
Prefetch with staleTime
Prevent refetching data that’s already fresh:
queryClient.prefetchQuery({
queryKey: ['post', id],
queryFn: () => fetchPost(id),
staleTime: 5 * 60 * 1000, // Only prefetch if cached data is older than 5 minutes
})
Setting staleTime in prefetch prevents unnecessary network requests if the data is already in cache and fresh.
Prefetching Infinite Queries
Prefetch the first page of an infinite query:
queryClient.prefetchInfiniteQuery({
queryKey: ['comments', postId],
queryFn: ({ pageParam = 0 }) => fetchComments(postId, pageParam),
initialPageParam: 0,
getNextPageParam: (lastPage) => lastPage.nextCursor,
pages: 1, // Number of pages to prefetch
})
Router Integration
React Router
import { useQueryClient } from '@tanstack/react-query'
import { Link } from 'react-router-dom'
function PostsList() {
const queryClient = useQueryClient()
return (
<ul>
{posts.map((post) => (
<li key={post.id}>
<Link
to={`/posts/${post.id}`}
onMouseEnter={() => {
queryClient.prefetchQuery({
queryKey: ['post', post.id],
queryFn: () => fetchPost(post.id),
})
}}
>
{post.title}
</Link>
</li>
))}
</ul>
)
}
TanStack Router
import { createRoute } from '@tanstack/react-router'
const postRoute = createRoute({
getParentRoute: () => rootRoute,
path: 'posts/$postId',
loader: async ({ params, context }) => {
// Prefetch in the loader
await context.queryClient.ensureQueryData({
queryKey: ['post', params.postId],
queryFn: () => fetchPost(params.postId),
})
},
})
Next.js App Router
Prefetch in Server Components:
// app/posts/page.tsx
import { QueryClient, dehydrate, HydrationBoundary } from '@tanstack/react-query'
export default async function PostsPage() {
const queryClient = new QueryClient()
// Prefetch on the server
await queryClient.prefetchQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
})
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<PostsList />
</HydrationBoundary>
)
}
Prefetch vs ensureQueryData
prefetchQuery
Fires and forgets - doesn’t return data:
// Returns void, doesn't wait for data
queryClient.prefetchQuery({
queryKey: ['post', id],
queryFn: () => fetchPost(id),
})
ensureQueryData
Returns the data, waits if needed:
// Returns Promise<Data>, waits for data
const data = await queryClient.ensureQueryData({
queryKey: ['post', id],
queryFn: () => fetchPost(id),
})
Use ensureQueryData when you need the data immediately:
// In a router loader
loader: async ({ params, context }) => {
const post = await context.queryClient.ensureQueryData({
queryKey: ['post', params.postId],
queryFn: () => fetchPost(params.postId),
})
// Can use post data here
return { post }
}
Prefetching Multiple Queries
Prefetch several queries in parallel:
useEffect(() => {
Promise.all([
queryClient.prefetchQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
}),
queryClient.prefetchQuery({
queryKey: ['categories'],
queryFn: fetchCategories,
}),
queryClient.prefetchQuery({
queryKey: ['user'],
queryFn: fetchUser,
}),
])
}, [])
Conditional Prefetching
Only prefetch under certain conditions:
function PostLink({ post, prefetch = true }) {
const queryClient = useQueryClient()
const handleHover = () => {
if (!prefetch) return
// Only prefetch if not already in cache
const cachedData = queryClient.getQueryData(['post', post.id])
if (!cachedData) {
queryClient.prefetchQuery({
queryKey: ['post', post.id],
queryFn: () => fetchPost(post.id),
})
}
}
return <a onMouseEnter={handleHover}>{post.title}</a>
}
Prefetching from Query Data
Use existing query data to prefetch related queries:
function usePosts() {
const queryClient = useQueryClient()
return useQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
onSuccess: (posts) => {
// Prefetch individual posts
posts.forEach((post) => {
queryClient.setQueryData(['post', post.id], post)
})
},
})
}
Use setQueryData instead of prefetchQuery when you already have the data. It’s instant and doesn’t require a network request.
Background Prefetching
Prefetch data in the background while user is idle:
function useBackgroundPrefetch() {
const queryClient = useQueryClient()
useEffect(() => {
const timeout = setTimeout(() => {
// Prefetch after user has been on page for 2 seconds
queryClient.prefetchQuery({
queryKey: ['suggested-posts'],
queryFn: fetchSuggestedPosts,
})
}, 2000)
return () => clearTimeout(timeout)
}, [queryClient])
}
Debounced Prefetching
Debounce prefetch calls to avoid excessive requests:
import { useDebouncedCallback } from 'use-debounce'
function SearchInput() {
const queryClient = useQueryClient()
const prefetchSearch = useDebouncedCallback((query: string) => {
queryClient.prefetchQuery({
queryKey: ['search', query],
queryFn: () => searchPosts(query),
})
}, 300)
return (
<input
onChange={(e) => prefetchSearch(e.target.value)}
placeholder="Search..."
/>
)
}
Prefetch with Custom Hook
Create a reusable prefetch hook:
function usePrefetch() {
const queryClient = useQueryClient()
return useCallback(
(queryKey: QueryKey, queryFn: QueryFunction) => {
queryClient.prefetchQuery({
queryKey,
queryFn,
staleTime: 10 * 1000,
})
},
[queryClient]
)
}
// Usage
function PostsList() {
const prefetch = usePrefetch()
return (
<ul>
{posts.map((post) => (
<li onMouseEnter={() => prefetch(['post', post.id], () => fetchPost(post.id))}>
{post.title}
</li>
))}
</ul>
)
}
Best Practices
- Set appropriate staleTime - Prevent refetching fresh data
- Prefetch on user intent - Hover, focus, route changes
- Use ensureQueryData when you need the data immediately
- Don’t over-prefetch - Balance UX with bandwidth usage
- Prefetch in parallel when possible
- Consider user’s connection - Maybe skip prefetch on slow connections
// Check connection before prefetching
const shouldPrefetch = navigator.connection?.effectiveType !== 'slow-2g'
if (shouldPrefetch) {
queryClient.prefetchQuery({
queryKey: ['post', id],
queryFn: () => fetchPost(id),
})
}
Next Steps