如何在Next.js中使用React Query实现服务端渲染?
在Next.js中用React Query实现服务端渲染的解决方案
因为React Query的hooks依赖客户端环境,服务端组件无法直接使用,下面是几种可行的实现方案:
方案1:服务端预取数据 + React Query 脱水/注水(App Router)
在服务端组件中预取数据,通过dehydrate将QueryClient中的数据序列化,再传给客户端组件用Hydrate注入,让React Query复用服务端数据,避免重复请求。
代码示例
服务端组件(page.tsx):
import { dehydrate, QueryClient } from '@tanstack/react-query' import TodoClientComponent from './TodoClientComponent' export default async function TodoPage() { const queryClient = new QueryClient() // 服务端预取数据,queryKey需和客户端useQuery保持一致 await queryClient.prefetchQuery({ queryKey: ['todos'], queryFn: () => fetch('https://api.example.com/todos').then(res => res.json()), }) const dehydratedState = dehydrate(queryClient) return <TodoClientComponent dehydratedState={dehydratedState} /> }
客户端组件(TodoClientComponent.tsx,需添加'use client'指令):
'use client' import { Hydrate, QueryClient, QueryClientProvider } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query' export default function TodoClientComponent({ dehydratedState }: { dehydratedState: any }) { const queryClient = new QueryClient() return ( <QueryClientProvider client={queryClient}> <Hydrate state={dehydratedState}> <TodoList /> </Hydrate> </QueryClientProvider> ) } function TodoList() { // 自动复用服务端预取的数据,无需重复请求 const { data } = useQuery({ queryKey: ['todos'], queryFn: () => fetch('https://api.example.com/todos').then(res => res.json()), }) return ( <ul> {data?.map(todo => <li key={todo.id}>{todo.title}</li>)} </ul> ) }
方案2:服务端组件传初始数据给客户端组件
在服务端组件直接获取数据,将数据作为props传给客户端组件,客户端用useQuery的initialData参数使用该数据,同时保留React Query的缓存能力。
代码示例
服务端组件(page.tsx):
import TodoClientComponent from './TodoClientComponent' export default async function TodoPage() { const todos = await fetch('https://api.example.com/todos').then(res => res.json()) return <TodoClientComponent initialTodos={todos} /> }
客户端组件(TodoClientComponent.tsx):
'use client' import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query' export default function TodoClientComponent({ initialTodos }: { initialTodos: any[] }) { const queryClient = new QueryClient() return ( <QueryClientProvider client={queryClient}> <TodoList initialTodos={initialTodos} /> </QueryClientProvider> ) } function TodoList({ initialTodos }: { initialTodos: any[] }) { const { data } = useQuery({ queryKey: ['todos'], queryFn: () => fetch('https://api.example.com/todos').then(res => res.json()), initialData: initialTodos, // 直接使用服务端传来的初始数据 }) return ( <ul> {data?.map(todo => <li key={todo.id}>{todo.title}</li>)} </ul> ) }
方案3:Page Router 下的实现(Next.js 12及更早)
若使用Page Router,可通过getServerSideProps预取数据并脱水,再传给页面组件:
// pages/todos.tsx import { dehydrate, Hydrate, QueryClient, QueryClientProvider } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query' export async function getServerSideProps() { const queryClient = new QueryClient() await queryClient.prefetchQuery({ queryKey: ['todos'], queryFn: () => fetch('https://api.example.com/todos').then(res => res.json()), }) return { props: { dehydratedState: dehydrate(queryClient), }, } } export default function TodosPage({ dehydratedState }) { const queryClient = new QueryClient() return ( <QueryClientProvider client={queryClient}> <Hydrate state={dehydratedState}> <TodoList /> </Hydrate> </QueryClientProvider> ) } function TodoList() { const { data } = useQuery({ queryKey: ['todos'], queryFn: () => fetch('https://api.example.com/todos').then(res => res.json()), }) return ( <ul> {data?.map(todo => <li key={todo.id}>{todo.title}</li>)} </ul> ) }
注意事项
- 服务端组件不能直接使用
useQuery等React Query hooks,必须在客户端组件中调用。 - 脱水/注水方案能完整保留React Query的缓存、自动重新获取等特性,适合复杂场景;传初始数据的方式更简单,适合基础场景。
- 确保服务端和客户端的
queryKey完全一致,否则React Query无法匹配缓存数据。
内容的提问来源于stack exchange,提问作者arjun tv
相关产品推荐
相关产品推荐

