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

使用redux-persist+Redux Toolkit时服务端HTML元素无法渲染

问题解决:Redux-Persist PersistGate 导致服务端HTML无法正常渲染

核心原因

PersistGate 默认会等待 redux-persist 完成存储恢复后才渲染应用内容。在服务端渲染场景下,即便你配置了 noopStorage,PersistGate 的等待逻辑仍会导致服务端返回的是 loading 状态的空内容,而非实际页面HTML;客户端做hydrate时,还会因为服务端与客户端初始状态不匹配,导致页面内容被替换为空。

解决方案

1. 服务端跳过PersistGate,仅客户端使用

在应用入口文件中区分服务端与客户端环境,服务端直接渲染页面组件,客户端再用PersistGate包裹:

import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
import { store, persistor } from './store';

function App({ Component, pageProps, isServer }) {
  return (
    <Provider store={store}>
      {isServer ? (
        <Component {...pageProps} />
      ) : (
        <PersistGate loading={null} persistor={persistor}>
          <Component {...pageProps} />
        </PersistGate>
      )}
    </Provider>
  );
}

// 以Next.js为例,通过getInitialProps判断服务端环境
App.getInitialProps = async (appContext) => {
  const appProps = await (typeof App.getInitialProps === 'function' ? App.getInitialProps(appContext) : {});
  return {
    ...appProps,
    isServer: typeof window === 'undefined'
  };
};

export default App;

2. 配置PersistGate的loading属性,确保服务端渲染内容匹配

如果必须在服务端使用PersistGate,设置loading为服务端渲染时的实际页面内容(或null),避免返回空内容:

// 入口文件中
<PersistGate loading={<Component {...pageProps} />} persistor={persistor}>
  <Component {...pageProps} />
</PersistGate>

这种方式下,服务端会直接渲染loading中的内容,客户端等待persist完成后替换为正常内容,同时保证hydrate时的内容匹配。

3. 优化Store配置,避免服务端不必要的persist逻辑

你已经配置了noopStorage,还可以进一步在服务端创建store时跳过persistReducer,直接使用原始rootReducer:

// store.js 修改
import { persistReducer, persistStore } from "redux-persist";
import authUserReducer from "./authUser";
import { combineReducers, configureStore } from "@reduxjs/toolkit";
import createWebStorage from "redux-persist/lib/storage/createWebStorage";
import thunk from "redux-thunk";

const rootReducer = combineReducers({
  authUser: authUserReducer,
});

const createNoopStorage = () => {
  return {
    getItem(_key) { return Promise.resolve(null); },
    setItem(_key, value) { return Promise.resolve(value); },
    removeItem(_key) { return Promise.resolve(); },
  };
};

const storage = typeof window !== "undefined" ? createWebStorage("local") : createNoopStorage();
const persistConfig = { key: "root", storage };
const persistedReducer = persistReducer(persistConfig, rootReducer);

let store;

const createStore = (preloadedState) => {
  if (typeof window === 'undefined') {
    // 服务端不使用persistReducer
    return configureStore({
      reducer: rootReducer,
      devTools: false,
      middleware: [thunk],
      preloadedState
    });
  } else {
    // 客户端使用persistedReducer
    return configureStore({
      reducer: persistedReducer,
      devTools: process.env.NODE_ENV !== "production",
      middleware: [thunk],
      preloadedState
    });
  }
};

export const initializeStore = (preloadedState) => {
  let _store = store ?? createStore(preloadedState);
  
  // 客户端有预加载状态时合并状态
  if (preloadedState && store) {
    _store = createStore({
      ...store.getState(),
      ...preloadedState
    });
    store = undefined;
  }

  // 服务端每次请求创建新store
  if (typeof window === 'undefined') return _store;
  
  // 客户端只创建一次store
  if (!store) store = _store;

  return _store;
};

export const persistor = typeof window !== 'undefined' ? persistStore(initializeStore()) : null;

之后在入口文件中,服务端使用initializeStore创建不带persist的store,客户端使用带persist的store和persistor。

验证要点

  • 确认服务端返回的HTML包含完整页面内容,而非PersistGate的loading占位符
  • 客户端hydrate时没有内容闪烁或被替换为空的情况
  • 客户端能正常恢复redux持久化状态

内容的提问来源于stack exchange,提问作者Farrukh Ahmad

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 11:30:50