Next.js App Router pattern với React Query: Server Components fetch data và dehydrate cache, Client Components nhận hydrated cache — không có loading flash.
Pattern đầy đủ:
- Tạo
getQueryClient()factory vớicache()từ React để mỗi request server có QueryClient riêng (tránh data leak giữa users). - Server Component:
const queryClient = getQueryClient(); await queryClient.prefetchQuery({ queryKey: ['posts'], queryFn: fetchPosts }); return <HydrationBoundary state={dehydrate(queryClient)}><PostList /></HydrationBoundary>. - Client Component dùng
useQuerybình thường — nhận data từ hydrated cache ngay, không request thêm nếu data còn fresh
Streaming hydration: có thể dùng với Suspense để stream từng phần page.
RSC considerations: không thể dùng React Query hooks trong RSC (chỉ là async functions) — hooks chỉ trong Client Components.
Nên prefetch ở server những gì quan trọng cho initial render, lazy load phần ít quan trọng hơn ở client.
Lưu ý: nếu không dùng cache() cho QueryClient factory, server-side QueryClient sẽ được share giữa requests → data leaking.
Next.js App Router pattern with React Query: Server Components fetch data and dehydrate the cache; Client Components receive the hydrated cache — no loading flash.
Full pattern:
- Create a
getQueryClient()factory using React'scache()so each server request gets its own QueryClient (prevents data leaking between users). - Server Component:
const queryClient = getQueryClient(); await queryClient.prefetchQuery({ queryKey: ['posts'], queryFn: fetchPosts }); return <HydrationBoundary state={dehydrate(queryClient)}><PostList /></HydrationBoundary>. - Client Component uses
useQuerynormally — receives data from the hydrated cache immediately, with no additional request if data is still fresh
Streaming hydration: works with Suspense to stream page sections.
RSC considerations: React Query hooks cannot be used in RSCs (they are async functions only) — hooks are Client Component only.
Prefetch on the server what matters for initial render; lazy load less critical parts on the client.
Pitfall: if you don't use cache() for the QueryClient factory, a single server-side QueryClient will be shared across all requests → data leaking between users.