Capítulo 33 de 456
Use TanStack Query pra buscar dado em Client Components, fornecer dado inicial via <HydrationBoundary> a partir de Server Components, e coordenar mutations do browser com dado server cacheado através de query key/tag compartilhados. Com Cache Components, exige um helper de hidratação manual pra evitar leitura de Date.now() durante prerender.
QueryClientProvider + getQueryClient(): um QueryClient novo por render server, um único reaproveitado no browser (typeof window === 'undefined' decide).useQuery({queryKey, queryFn, enabled}): enabled atrasa a requisição até haver input, equivalente à key condicional do SWR.useSuspenseQuery: delega loading ao Suspense boundary; propaga erro ao error boundary mais próximo.prefetchQuery + dehydrate + <HydrationBoundary>: fornece dado do servidor; prefetchQuery não é awaitado (void) para não bloquear o render; shouldDehydrateQuery customizado inclui queries pending.queryOptions(): helper que agrupa queryKey+queryFn+staleTime num contrato único reusado por prefetch e hook.staleTime: evita refetch imediato no client após hidratação (equivalente a revalidateIfStale do SWR, mas com janela de tempo).useMutation + onMutate/onError: update otimista manual: cancela queries em voo, salva valor anterior em context, escreve valor novo, reverte em onError.dehydrate() lê Date.now(): com Cache Components isso causa current-time prerender error; use um helper próprio (getHydrationUpdatedAt com use cache) para timestamp cacheado por tag.'use client'
let browserQueryClient: QueryClient | undefined
function getQueryClient() {
if (typeof window === 'undefined') return new QueryClient()
browserQueryClient ??= new QueryClient()
return browserQueryClient
}
import { queryOptions } from '@tanstack/react-query'
export const productCache = {
key: (id: string) => ['product', id] as const,
tag: (id: string) => `product:${id}`,
options: (id: string) =>
queryOptions({
queryKey: productCache.key(id),
queryFn: async () => {
const res = await fetch(`/api/products/${id}`)
if (!res.ok) throw new Error('Failed to fetch product')
return res.json()
},
staleTime: 30_000,
}),
}
async function getHydrationUpdatedAt(tags: string[]) {
'use cache'
cacheTag(...tags)
cacheLife('max')
return Date.now()
}
| Camada | Owns | Controle |
|---|---|---|
Next.js server cache (cacheLife) | dado + output RSC | revalidate/expire |
Next.js client cache (cacheLife) | payload RSC prefetched | stale |
| TanStack Query browser cache | valor sob queryKey | staleTime, invalidateQueries, mutations |
dehydrate() direto com Cache Components habilitado: lê Date.now() durante prerender e gera current-time prerender error. Use o helper de hidratação manual com use cache.useSuspenseQuery no mesmo componente: rodam sequencialmente (waterfall); separe em componentes irmãos ou use useSuspenseQueries.pending (não awaited) pra Server Component prefetch.staleTime evita refetch client logo após hidratação; não precisa bater com cacheLife do servidor, que é camada independente.dehydrate() puro; construa o hydration state manualmente com timestamp cacheado por tag.onMutate/onError exige cancelar queries em voo antes de sobrescrever, senão uma resposta tardia pode sobrescrever o valor otimista.updateTag no Server Action invalida o server read cacheado com a tag do mesmo contrato usado no client.