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

Next.js中结合Redux实现SSR服务端数据获取方法

Next.js + Redux 服务端渲染实现方案

核心逻辑:getServerSideProps运行在Node服务端,无法直接访问浏览器端的Redux单例store,需要为每次服务端请求创建独立store实例,提前dispatch异步action拉取数据,再将序列化后的store状态传回客户端完成水合,保证前后端状态一致。


1. 改造Redux store创建逻辑,支持服务端多实例

禁止导出全局单例store,否则服务端多用户请求会出现状态串流问题,改为工厂函数创建实例:

// store/index.js 改造示例
import { createStore, applyMiddleware, combineReducers } from 'redux';
import thunk from 'redux-thunk';
// 引入项目中已有的reducer
import productReducer from './reducers/ProductReducer';
import preLoadingReducer from './reducers/PreLoadingReducer';
import categoryReducer from './reducers/CategoryReducer';

const rootReducer = combineReducers({
  products: productReducer,
  preLoading: preLoadingReducer,
  categories: categoryReducer
});

// 接收预加载状态参数,支持服务端状态注入
export const initializeStore = (preloadedState = {}) => {
  return createStore(
    rootReducer,
    preloadedState,
    applyMiddleware(thunk) // 项目用的其他中间件(如saga)正常放在这里
  )
}

2. 改造_app.js入口,统一处理Redux状态水合

在应用入口接收服务端传回的初始Redux状态,初始化客户端store,避免hydration不匹配报错:

// pages/_app.js
import { Provider } from 'react-redux';
import { initializeStore } from '../store';
import { useMemo } from 'react';

function MyApp({ Component, pageProps }) {
  // 客户端仅初始化一次store,用服务端传回的状态做初始值
  const store = useMemo(() => {
    return initializeStore(pageProps.initialReduxState || {});
  }, [pageProps.initialReduxState]);

  return (
    <Provider store={store}>
      <Component {...pageProps} />
    </Provider>
  )
}

export default MyApp;

3. 重写页面getServerSideProps逻辑,服务端预拉数据

在服务端函数中创建独立store实例,直接复用你已经写好的Redux action拉取数据,完成后将store状态回传:

// 页面顶部引入store工厂函数
import { initializeStore } from '../../../store';

export const getServerSideProps = async (context) => {
  // 从上下文取路由参数,和客户端router.query.maincat拿到的值完全一致
  const { maincat } = context.query;
  // 为当前请求创建独立store实例
  const store = initializeStore();

  try {
    // 直接dispatch项目中已有的异步action,和客户端写法完全一致
    await store.dispatch(ProductAction.fetchMainCatProduct(maincat));
    // 需要服务端预加载的其他数据(比如品牌、筛选项)也可以在这里dispatch
    // await store.dispatch(CategoryAction.fetchBrandsAndFilters());
  } catch (err) {
    // 接口报错可以自定义处理,比如返回404页面
    return { notFound: true }
  }

  // 拿到所有action执行完成后的全量store状态
  const initialReduxState = store.getState();

  return {
    props: {
      initialReduxState, // 必须传回给客户端做水合
      // 其他自定义页面props正常添加
    }
  }
}

4. 调整页面客户端加载逻辑,避免重复请求

服务端已经预拉了首屏商品数据,首屏加载不需要再重复跑客户端请求,只在客户端路由跳转时触发加载即可。
首先在react引入中加上useRef:

import { useCallback, useEffect, useState, useRef } from "react";

然后修改原来的useEffect逻辑:

// 标记是否为首次水合渲染
const isInitialMount = useRef(true);
useEffect(() => {
  // 首屏SSR已经带了数据,直接跳过加载逻辑
  if (isInitialMount.current) {
    isInitialMount.current = false;
    setItems(false);
    props.setPreLoading(false);
    return;
  }
  // 仅客户端路由切换时走加载逻辑
  props.setPreLoading(true);
  setItems(true);
  loadProducts().then(() => {
    setItems(false);
    setTimeout(() => {
      props.setPreLoading(false);
    }, 1000);
  });
}, [dispatch, setItems, loadProducts, MainCat]);

注意事项

  • 服务端必须每次请求新建store实例,绝对不能复用全局单例,否则会出现不同用户的状态互相污染
  • 服务端dispatch异步action必须加await等请求完成后再取store.getState(),否则拿到的是空初始状态
  • Redux中存储的状态必须是可序列化的,不要存函数、DOM节点这类特殊对象,否则序列化传回客户端时会报错
  • 排序、筛选这类纯客户端交互的逻辑不需要改动,和之前写法一致,正常dispatch对应action即可,只有首屏直出需要的数据在服务端预拉

内容的提问来源于stack exchange,提问作者Amir Hossein

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 07:01:02