Next.js 13(无App目录)Redux SSR状态水合不匹配问题求助
解决方案:手动实现服务端Redux状态传递,避免SSR内容不匹配
核心思路是让客户端初始化Redux Store时直接复用服务端传递的最终状态,而非等待useEffect水合,确保客户端首次渲染的状态与服务端完全一致,从根源解决内容不匹配问题。以下是具体实现步骤:
1. 改造Store创建函数,支持预加载状态
将固定初始状态的Store改为可接收预加载状态的工厂函数,让服务端和客户端都能传入对应状态:
// store.ts import { configureStore } from '@reduxjs/toolkit'; import contentReducer from './slices/contentSlice'; // 工厂函数:接收预加载状态,返回Store实例 export const createStore = (preloadedState?: RootState) => { return configureStore({ reducer: { content: contentReducer, }, preloadedState, }); }; // 导出类型,方便TS类型推导 export type RootState = ReturnType<typeof createStore>['getState']; export type AppDispatch = ReturnType<typeof createStore>['dispatch'];
2. 在getServerSideProps中生成并传递服务端状态
在服务端请求CMS数据、更新Store后,将Store的最终状态序列化,作为props传递给页面:
// pages/index.tsx import { useMemo } from 'react'; import { Provider } from 'react-redux'; import { createStore, RootState } from '../store'; import { fetchContent } from '../store/slices/contentSlice'; import ContentDisplay from '../components/ContentDisplay'; // 页面Props类型:接收服务端传递的Redux初始状态 type HomeProps = { initialReduxState: RootState; }; export default function Home({ initialReduxState }: HomeProps) { // 用useMemo确保客户端仅初始化一次Store,避免重复创建 const store = useMemo(() => createStore(initialReduxState), [initialReduxState]); return ( <Provider store={store}> <ContentDisplay /> </Provider> ); } export async function getServerSideProps() { // 服务端每次请求都创建新Store实例,避免请求间状态污染 const serverStore = createStore(); // 从CMS获取数据并更新服务端Store await serverStore.dispatch(fetchContent()); // 获取服务端Store的最终状态,传递给页面 const initialReduxState = serverStore.getState(); return { props: { initialReduxState, }, }; }
3. 异步数据请求的Slice实现示例
确保CMS数据请求是Redux Toolkit的异步Action,保证服务端能正确更新状态:
// store/slices/contentSlice.ts import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; import type { RootState } from '../store'; // 异步Action:从CMS拉取内容 export const fetchContent = createAsyncThunk( 'content/fetchContent', async () => { const res = await fetch('你的CMS API地址'); return res.json(); } ); interface ContentState { data: { title: string; content: string } | null; // 替换为你的内容类型 loading: boolean; error: string | null; } const initialState: ContentState = { data: null, loading: false, error: null, }; const contentSlice = createSlice({ name: 'content', initialState, reducers: {}, extraReducers: (builder) => { builder .addCase(fetchContent.pending, (state) => { state.loading = true; state.error = null; }) .addCase(fetchContent.fulfilled, (state, action) => { state.loading = false; state.data = action.payload; }) .addCase(fetchContent.rejected, (state, action) => { state.loading = false; state.error = action.error.message || '拉取内容失败'; }); }, }); export default contentSlice.reducer;
关键注意事项
- 状态可序列化:确保Redux状态中没有不可序列化的内容(如函数、Date对象、DOM元素)。如果有日期,可在服务端转为ISO字符串,客户端渲染时再转回Date;RTK默认开启的
serializableCheck中间件会帮你检查违规内容。 - 服务端Store隔离:每次
getServerSideProps调用必须创建新的Store实例,绝对不能复用全局Store,否则会导致不同用户请求间的状态污染。 - TS类型安全:通过导出的
RootState和AppDispatch类型,确保页面Props、组件中使用的useSelector/useDispatch都有正确的类型推导,避免TS报错。
内容的提问来源于stack exchange,提问作者Hamza Mubeen
相关产品推荐
相关产品推荐

