TanStack Query
Preserve types through SSR hydration
TanStack Query's SSR hydration serializes your query cache to JSON and back. Without fatline, your Date, Set, and Map values become strings and arrays.
The Problem
// Server: Query returns Date
const { data } = useQuery({
queryKey: ['user'],
queryFn: () => ({ createdAt: new Date() })
})
// After SSR hydration:
data.createdAt instanceof Date // false ← it's a string nowSetup
import { serialize, deserialize } from 'fatline'
import { QueryClient } from '@tanstack/react-query'
const queryClient = new QueryClient({
defaultOptions: {
dehydrate: {
serializeData: serialize,
},
hydrate: {
deserializeData: deserialize,
},
},
})Now your types survive hydration:
data.createdAt instanceof Date // trueNext.js App Router
// app/providers.tsx
'use client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { serialize, deserialize } from 'fatline'
import { useState } from 'react'
function makeQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000,
},
dehydrate: {
serializeData: serialize,
},
hydrate: {
deserializeData: deserialize,
},
},
})
}
let browserQueryClient: QueryClient | undefined
function getQueryClient() {
if (typeof window === 'undefined') {
return makeQueryClient()
}
return (browserQueryClient ??= makeQueryClient())
}
export function Providers({ children }: { children: React.ReactNode }) {
const queryClient = getQueryClient()
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
)
}// app/layout.tsx
import { Providers } from './providers'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}With Prefetching
// app/users/page.tsx
import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query'
import { serialize, deserialize } from 'fatline'
import { UserList } from './user-list'
export default async function UsersPage() {
const queryClient = new QueryClient({
defaultOptions: {
dehydrate: { serializeData: serialize },
hydrate: { deserializeData: deserialize },
},
})
await queryClient.prefetchQuery({
queryKey: ['users'],
queryFn: getUsers,
})
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<UserList />
</HydrationBoundary>
)
}With tRPC
If you're using tRPC with the fatline transformer, types are already preserved through tRPC's serialization. You only need this setup if you're using TanStack Query directly without tRPC.
// tRPC already handles serialization
import { transformer } from 'fatline/trpc'
const t = initTRPC.create({ transformer })Streaming SSR
fatline works with TanStack Query's streaming SSR. Pending queries are dehydrated with their data intact:
// Pending queries serialize correctly
await queryClient.prefetchQuery({
queryKey: ['users'],
queryFn: getUsers,
})
// Even if the query hasn't resolved yet, the dehydrated state
// will serialize any partial data correctly
const dehydratedState = dehydrate(queryClient)