You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 15:50:32