Hydrating client caches with server-fetched initial data means seeding a client data library with data the server already loaded. The first render shows real content instead of a loading state, and the client skips the duplicate request it would otherwise make. It is the bridge between server-side fetching and a client-side data library.
Without hydration, a page that fetches on the server and again on the client downloads the same data twice. Hydration passes the server result into the client cache so both layers agree from the start. The tradeoff is a small amount of wiring, paid back by a faster first paint and one less request per page view.
Hydrating TanStack Query with initialData
The simplest approach is the initialData option. Pass the server-fetched value straight into useQuery and it becomes the query's first result. This is the lightest form of hydration and needs no extra providers or serialization.
// app/posts/page.tsx
import PostsClient from "./posts-client";
import type { Post } from "./types";
export default async function PostsPage() {
const res = await fetch("https://api.example.com/posts");
const posts: Post[] = await res.json();
return <PostsClient initialPosts={posts} />;
}The server page fetches the posts and passes them to the client component as props, with Post declared once in a shared types module. Props must stay serializable, and an array of plain objects qualifies.
// app/posts/posts-client.tsx
"use client";
import { useQuery } from "@tanstack/react-query";
import type { Post } from "./types";
export default function PostsClient({ initialPosts }: { initialPosts: Post[] }) {
const { data } = useQuery({
queryKey: ["posts"],
queryFn: () => fetch("/api/posts").then((res) => res.json()),
initialData: initialPosts,
});
return <ul>{data.map((post) => <li key={post.id}>{post.title}</li>)}</ul>;
}initialData makes the query start with the server value, so the list renders on the first paint instead of after a round trip. The client still revalidates on its normal schedule.
One detail matters here: initialData is treated as fresh data, not as a placeholder. Since a query is stale by default the moment it loads, set staleTime if you do not want an immediate background refetch on mount.
Why hydration beats duplicate fetching
A page that fetches on the server and then again on the client pays for the same request twice and flashes a loading state in between. Hydration reuses the server result, so the client cache starts warm and only refetches when its own policy says so.
The visible difference is the first paint. Without hydration the user sees a skeleton that is replaced a few hundred milliseconds later, even though the server already had the data in the HTML it just sent.
Hydrating SWR with cacheData
SWR seeds its cache through the cacheData option on SWRConfig, which is available in Server Components even though the hooks are not. The preload helper starts the request and returns the cache entry keyed for you.
// app/dashboard/layout.tsx
import { preload, SWRConfig } from "swr";
import { fetchUser } from "@/lib/user";
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
const cacheData = preload("/api/user", fetchUser);
return <SWRConfig value={{ cacheData }}>{children}</SWRConfig>;
}A client component calling useSWR with the /api/user key reads this value for its first render instead of showing a loading state. Because preload is not awaited, the layout itself never blocks on the request.
The key must match exactly between the cache entry and the hook, otherwise SWR treats it as a miss and fetches anyway.
Choosing a library
Both patterns achieve the same goal. TanStack Query's initialData is explicit per query, while SWR's cacheData is a map keyed by the request key.
Use whichever library the app already uses, and keep the server as the single place that owns the initial fetch. For the setup of each library, see SWR and TanStack Query.
Rune AI
Key Insights
- Hydration seeds the client cache with server-fetched data.
- It avoids a duplicate initial request and a loading flash.
- TanStack Query hydrates with initialData on useQuery.
- SWR hydrates with cacheData on SWRConfig.
- Revalidation still runs normally after hydration.
Frequently Asked Questions
Why hydrate the client cache?
Does hydration stop revalidation?
Which option hydrates TanStack Query?
Conclusion
Hydration seeds a client data cache with server-fetched data, so the first render is already populated. TanStack Query uses initialData, while SWR uses the cacheData option on SWRConfig.
More in this topic
`generateMetadata` Explained with Real Examples
What generateMetadata does, when it runs, and how to use it for real routes: awaited params, deduplicated data fetching, extending parent metadata, and returning a 404 from metadata.
Canonical URLs in Next.js: `metadataBase`, `alternates.canonical`, and Dynamic Pages
How canonical URLs work in the Next.js App Router: setting metadataBase once, writing alternates.canonical per route, handling dynamic segments, and what happens when the base URL is missing.
Open Graph and Twitter Card Metadata in Next.js
How to write Open Graph and Twitter card metadata in the Next.js App Router: the openGraph and twitter fields, automatic card defaults, article tags, and image merge rules.