From harness-claude
Hydrates TanStack Query client cache from server-prefetched data using dehydrate/hydrate and HydrationBoundary. Eliminates loading states in Next.js App Router SSR and enables hover prefetching.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Hydrate the client cache from server-fetched data using dehydrate/hydrate and HydrationBoundary
Provides expertise in TanStack Query for React/Next.js data fetching, caching (staleTime/gcTime), mutations, optimistic updates, cache invalidation, and App Router SSR hydration.
Implements TanStack Query v5 in React apps for API data fetching, server state caching, mutations, optimistic updates, infinite scroll, streaming AI responses, and tRPC v11 integration.
Provides TanStack Query (React Query) best practices for query keys, caching, mutations, error handling, prefetching, and SSR in React apps. Useful for data fetching and server state management.
Share bugs, ideas, or general feedback.
Hydrate the client cache from server-fetched data using dehydrate/hydrate and HydrationBoundary
QueryClient in the Server Component and prefetch data with queryClient.prefetchQuery().<HydrationBoundary> component — it hydrates the client-side QueryClient automatically.dehydrate(queryClient) from @tanstack/react-query to serialize the cache for transport to the client.useQuery() with the same key used during prefetch — the data is available immediately with no loading state.queryClient.prefetchQuery() (not ensureQueryData) when you do not need the return value — it avoids an extra await.queryClient.prefetchQuery() in a onMouseEnter handler to warm the cache before navigation.staleTime to match your revalidation window — data prefetched at render time should not immediately re-fetch on mount.// 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>
);
}
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.
staleTime is critical for SSR: Without 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.
https://tanstack.com/query/latest/docs/framework/react/guides/prefetching