install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/codex/tanstack-prefetching" ~/.claude/skills/intense-visions-harness-engineering-tanstack-prefetching-804cf0 && rm -rf "$T"
manifest:
agents/skills/codex/tanstack-prefetching/SKILL.mdsource content
TanStack Query: Prefetching and SSR
Hydrate the client cache from server-fetched data using dehydrate/hydrate and HydrationBoundary
When to Use
- Eliminating loading spinners by pre-populating the cache with server-fetched data
- Integrating TanStack Query with Next.js App Router Server Components
- Prefetching data on hover before a user navigates to a page
- Passing server-rendered data to client-side queries without a separate API round-trip
Instructions
- In Next.js App Router, create a
in the Server Component and prefetch data withQueryClient
.queryClient.prefetchQuery() - Pass the dehydrated state to a
component — it hydrates the client-side<HydrationBoundary>
automatically.QueryClient - Use
fromdehydrate(queryClient)
to serialize the cache for transport to the client.@tanstack/react-query - In Client Components, call
with the same key used during prefetch — the data is available immediately with no loading state.useQuery() - Use
(notqueryClient.prefetchQuery()
) when you do not need the return value — it avoids an extra await.ensureQueryData - Prefetch on hover using
in aqueryClient.prefetchQuery()
handler to warm the cache before navigation.onMouseEnter - Set
to match your revalidation window — data prefetched at render time should not immediately re-fetch on mount.staleTime
// app/posts/page.tsx — Server Component with prefetch import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query'; import { postListOptions } from '@/queries/posts'; import { PostList } from './post-list'; export default async function PostsPage() { const queryClient = new QueryClient(); // Prefetch on the server — data is fetched once, not per-component await queryClient.prefetchQuery(postListOptions({})); return ( // Injects dehydrated cache into the page <HydrationBoundary state={dehydrate(queryClient)}> <PostList /> </HydrationBoundary> ); } // app/posts/post-list.tsx — Client Component, no loading state 'use client'; import { useQuery } from '@tanstack/react-query'; import { postListOptions } from '@/queries/posts'; export function PostList() { // Data is immediately available — cache was hydrated from server const { data: posts } = useQuery(postListOptions({})); return <ul>{posts?.map(p => <li key={p.id}>{p.title}</li>)}</ul>; } // Prefetch on hover — warm cache before navigation function PostLink({ id }: { id: string }) { const queryClient = useQueryClient(); return ( <Link href={`/posts/${id}`} onMouseEnter={() => queryClient.prefetchQuery(postDetailOptions(id))} > View Post </Link> ); }
Details
TanStack Query's SSR pattern solves the classic problem: server-rendered HTML contains data, but the client-side React Query cache is empty on mount, causing a flash of loading state as queries re-execute.
Dehydration/hydration mechanism:
dehydrate(queryClient) serializes successful query results into a plain JSON-serializable object. <HydrationBoundary state={dehydratedState}> deserializes it into the client's QueryClient on mount. The client queries then find their data already in cache and skip the initial fetch.
is critical for SSR: Without staleTime
staleTime, TanStack Query considers all queries stale immediately on mount and triggers background refetches — wasting the server prefetch. Set staleTime to at least as long as the server render takes (e.g., Infinity for fully static data, or a few minutes for semi-fresh data).
New QueryClient per request: In Server Components, create a
new QueryClient() per page render, not a shared singleton. A singleton would leak data between user requests in a server-side Node.js process.
Multiple prefetches: Prefetch all queries needed by a page segment at once using
Promise.all:
await Promise.all([ queryClient.prefetchQuery(postListOptions({})), queryClient.prefetchQuery(categoriesOptions()), ]);
Router-level prefetch: For non-Next.js apps, TanStack Router has built-in prefetch integration via
loader functions that call ensureQueryData. This achieves the same effect without manual dehydrate/hydrate wiring.
Source
https://tanstack.com/query/latest/docs/framework/react/guides/prefetching
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.