如何在Next.js SSR中正确使用RTK Query?配置后SSR未生效
问题:Next.js + RTK Query SSR 预取数据不生效,客户端重复发起请求
我正在学习RTK Query,在Next.js中结合SSR机制使用时遇到了问题:按照官方文档配置后,服务器端虽然预取了数据,但客户端仍会重复发送请求,SSR未生效。以下是我的代码:
blog.tsx(博客页面)
import Layout from "@components/Templates/Layout" import BlogsPage from "@components/Templates/BlogsPage" import store from "@redux/store" import { blogApi } from "@redux/api/blog" import { useGetBlogPostsQuery } from "@redux/api/blog" export default function Blog() { const { data, error, isLoading } = useGetBlogPostsQuery(); return ( <Layout> <BlogsPage posts={data.results} /> </Layout> ) } export const getServerSideProps = async () => { store.dispatch(blogApi.endpoints.getBlogPosts.initiate()) await Promise.all(store.dispatch(blogApi.util.getRunningQueriesThunk())) return { props: {} }; }
store.ts
import { configureStore } from '@reduxjs/toolkit' import { blogApi } from './api/blog'; const store = configureStore({ reducer: { [blogApi.reducerPath]: blogApi.reducer, } as any, middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat([ blogApi.middleware, ]), }) export default store;
blogApi.ts
import { httpClient } from "./httpClient"; const injectedEndpoints = httpClient.injectEndpoints({ endpoints: (builder) => ({ getBlogPosts: builder.query<any, void>({ query: () => ({ url: `/blog/post/`, method: "GET" }) }), }) }) export { injectedEndpoints as blogApi }; export const { useGetBlogPostsQuery, } = injectedEndpoints;
httpClient.ts
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' import { HYDRATE } from 'next-redux-wrapper' export const httpClient = createApi({ baseQuery: fetchBaseQuery({ baseUrl: process.env.NEXT_PUBLIC_API_URL+'api/v1' }), extractRehydrationInfo(action, { reducerPath }) { if (action.type === HYDRATE) { return action.payload[reducerPath] } }, endpoints: () => ({}), })
问题分析与解决方案
你遗漏了三个核心要点,导致SSR预取的数据无法被客户端复用:
1. 错误使用全局单例Store
Next.js SSR中每个请求都是独立的,全局单例Store会导致跨请求状态污染,同时客户端无法获取服务器预取的状态。必须将Store改为工厂函数,每个请求创建独立实例。
修改store.ts:
import { configureStore } from '@reduxjs/toolkit' import { blogApi } from './api/blog'; // 改为工厂函数,每次调用创建新Store export const makeStore = () => configureStore({ reducer: { [blogApi.reducerPath]: blogApi.reducer, }, middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(blogApi.middleware), }) // 导出类型方便后续使用 export type AppStore = ReturnType<typeof makeStore> export type RootState = ReturnType<AppStore['getState']>
2. 未将服务器预取的状态传递给客户端
getServerSideProps需要将服务器Store的状态作为props返回,客户端才能基于该状态初始化Store,复用预取的数据。同时要使用RTK Query的initializeApiState方法将预取的缓存注入客户端Store。
修改blog.tsx:
import Layout from "@components/Templates/Layout" import BlogsPage from "@components/Templates/BlogsPage" import { makeStore } from "@redux/store" import { blogApi } from "@redux/api/blog" import { useGetBlogPostsQuery } from "@redux/api/blog" import { Provider } from 'react-redux' export default function Blog({ initialState }) { const store = makeStore() // 将服务器预取的RTK Query缓存注入客户端Store store.dispatch(blogApi.util.initializeApiState(initialState[blogApi.reducerPath])) // 此时useGetBlogPostsQuery会自动读取缓存,不会重复请求 const { data, isLoading } = useGetBlogPostsQuery() return ( <Provider store={store}> <Layout> <BlogsPage posts={data?.results || []} isLoading={isLoading} /> </Layout> </Provider> ) } export const getServerSideProps = async () => { // 为当前请求创建独立Store const store = makeStore() // 发起请求并等待完成 const requestPromise = store.dispatch(blogApi.endpoints.getBlogPosts.initiate()) await requestPromise.unwrap() return { props: { // 将服务器Store的状态传递给客户端 initialState: store.getState() } }; }
3. 优化:确保客户端不重复请求
通过initializeApiState注入缓存后,RTK Query的useGetBlogPostsQuery会自动读取缓存,无需额外配置。如果需要强制控制,可以通过skip参数跳过请求:
const { data, isLoading } = useGetBlogPostsQuery(undefined, { skip: !!initialState[blogApi.reducerPath].queries['getBlogPosts(undefined)'] })
内容的提问来源于stack exchange,提问作者NavidMnzh
相关产品推荐
相关产品推荐

