3.3 KiB
3.3 KiB
pf-intent-prefetch: Prefetch on User Intent (Hover, Focus)
Priority: MEDIUM
Explanation
Prefetch data when users show intent to navigate (hover, focus) rather than waiting for click. This eliminates perceived loading time for likely next actions.
Bad Example
// No prefetching - data fetches on click
function PostList({ posts }: { posts: Post[] }) {
return (
<ul>
{posts.map(post => (
<li key={post.id}>
<Link to={`/posts/${post.id}`}>
{post.title}
</Link>
{/* User clicks, waits for data to load */}
</li>
))}
</ul>
)
}
Good Example
import { useQueryClient } from '@tanstack/react-query'
import { postQueries } from '@/lib/queries'
function PostList({ posts }: { posts: Post[] }) {
const queryClient = useQueryClient()
const handlePrefetch = (postId: number) => {
queryClient.prefetchQuery({
...postQueries.detail(postId),
staleTime: 60 * 1000, // Consider fresh for 1 minute
})
}
return (
<ul>
{posts.map(post => (
<li key={post.id}>
<Link
to={`/posts/${post.id}`}
onMouseEnter={() => handlePrefetch(post.id)}
onFocus={() => handlePrefetch(post.id)}
>
{post.title}
</Link>
</li>
))}
</ul>
)
}
Good Example: With TanStack Router
import { Link } from '@tanstack/react-router'
// TanStack Router has built-in prefetching
function PostList({ posts }: { posts: Post[] }) {
return (
<ul>
{posts.map(post => (
<li key={post.id}>
<Link
to="/posts/$postId"
params={{ postId: post.id }}
preload="intent" // Prefetch on hover/focus
>
{post.title}
</Link>
</li>
))}
</ul>
)
}
// Or set as router default
const router = createRouter({
routeTree,
defaultPreload: 'intent',
defaultPreloadDelay: 100, // Wait 100ms before prefetching
})
Good Example: Prefetch with Delay
function PostLink({ post }: { post: Post }) {
const queryClient = useQueryClient()
const timeoutRef = useRef<NodeJS.Timeout>()
const handleMouseEnter = () => {
// Delay prefetch to avoid unnecessary requests on quick mouse movements
timeoutRef.current = setTimeout(() => {
queryClient.prefetchQuery(postQueries.detail(post.id))
}, 100)
}
const handleMouseLeave = () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current)
}
}
return (
<Link
to={`/posts/${post.id}`}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{post.title}
</Link>
)
}
Prefetch Triggers
| Trigger | When to Use |
|---|---|
onMouseEnter |
Desktop, links/buttons user will likely click |
onFocus |
Keyboard navigation, accessibility |
onTouchStart |
Mobile, before navigation |
| Component mount | Likely next pages, wizard steps |
| Intersection Observer | Below-fold content |
Context
- Set appropriate
staleTimewhen prefetching to avoid immediate refetch - Consider mobile where hover isn't available
- Don't prefetch everything - focus on likely paths
- Prefetched data uses
gcTimefor retention - Watch network tab to verify prefetch timing