Episode ini membahas integrasi server-side rendering: dehydrate dan hydrate dengan HydrationBoundary untuk Next.js App Router, prefetch di server components, serta integrasi dengan Remix loaders dan pola framework-agnostic memakai query core.

Pada SSR (server-side rendering), halaman dirender di server lalu dikirim sebagai HTML. Masalahnya: kalau data hanya di-fetch di client, HTML awal kosong dan user melihat loading. Solusi TanStack Query: dehydrate cache di server, kirim serialisasinya bersama HTML, lalu hydrate di client supaya halaman langsung penuh tanpa fetch ulang.
Episode 14 membahas pola hydration untuk Next.js App Router dan Remix, ditambah cara memakai query core di luar framework.
Dua fungsi kunci: dehydrate membekukan cache menjadi serializable state, dan hydrate memulihkannya di sisi client. Keduanya dihubungkan oleh HydrationBoundary:
server: prefetch → dehydrate → kirim state serialized
client: hydrate state → render instan dari cacheDiagram server: prefetch → dehydrate menampilkan langkah di server, dan client: hydrate state langkah di client. HydrationBoundary adalah jembatan antar keduanya.
Di App Router, server components bisa prefetch ke cache yang dibuat di server. TanStack Query menyediakan pola helper getQueryClient untuk membungkus satu instance per request:
import { QueryClient, isServer } from "@tanstack/react-query"
function makeQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000,
},
},
})
}
let browserQueryClient = undefined
export function getQueryClient() {
if (isServer) {
return makeQueryClient()
}
if (!browserQueryClient) {
browserQueryClient = makeQueryClient()
}
return browserQueryClient
}getQueryClient mengembalikan instance baru di server (per request) dan instance yang sama di browser (per sesi). isServer membedakan konteks runtime. Ini mencegah cache bocor antar request di server dan cache berganda di client.
Dalam server component, prefetch lalu bungkus dengan HydrationBoundary yang menerima cache yang di-dehydrate:
import { dehydrate, HydrationBoundary } from "@tanstack/react-query"
import { getQueryClient } from "./get-query-client"
export default async function TodosPage() {
const queryClient = getQueryClient()
await queryClient.prefetchQuery({
queryKey: ["todos"],
queryFn: fetchTodos,
})
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<Todos />
</HydrationBoundary>
)
}queryClient.prefetchQuery mengisi cache di server, lalu dehydrate(queryClient) mengubah cache menjadi state serializable yang dikirim bersama HTML. HydrationBoundary state={dehydrate(queryClient)} memulihkan cache di client — komponen Todos yang memakai useQuery langsung menemukan data dan tidak melakukan fetch ulang.
Di Remix, data diambil di loader server dan diteruskan ke komponen. Untuk memadukannya dengan TanStack Query, seed cache dengan data loader lewat initialData di client:
export async function loader() {
const todos = await fetchTodos()
return { todos }
}
export default function Todos({ loaderData }) {
const { data } = useQuery({
queryKey: ["todos"],
queryFn: fetchTodos,
initialData: loaderData.todos,
staleTime: 60 * 1000,
})
return <TodoList todos={data} />
}initialData: loaderData.todos mengisi cache dari data yang sudah diambil loader. useQuery menampilkan data itu seketika dan menyegarkannya di latar belakang setelah staleTime habis. Pendekatan ini adalah cara paling sederhana memadukan Remix dan TanStack Query tanpa hydration boundary.
Karena logika inti hidup di @tanstack/query-core, semua adapter framework memakai konsep yang sama — query key, cache, observer. Ilmu yang kalian pelajari di series ini berlaku untuk Vue Query, Svelte Query, dan Solid Query tanpa perubahan konsep, hanya beda sintaks hook. Episode 19 akan membahas ekosistem adapter ini secara menyeluruh.
Warning
Di Next.js, jangan pernah membuat QueryClient baru di dalam setiap render komponen. Gunakan pola getQueryClient agar server membuat satu instance per request dan browser memakai satu instance untuk seluruh sesi — kalau tidak, cache akan reset terus menerus.
Episode 14 menjembatani TanStack Query dengan SSR: prefetch di server, dehydrate, hydrate lewat HydrationBoundary, pola getQueryClient untuk Next.js App Router, dan seeding cache dari loader Remix. Kalian juga melihat bahwa konsep intinya framework-agnostic.
Inti yang harus dibawa pulang:
dehydrate membekukan cache; hydrate memulihkannya di client.HydrationBoundary menghubungkan cache server dan client.getQueryClient membedakan instance server dan browser.initialData dari loader.Di episode 15 selanjutnya kita akan membahas security dan best practice — menangani 401/403 dengan refresh token, menghindari data sensitif berlebihan di cache, sanitasi error, serta best practice query key dan pemisahan server state versus client state.