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

Next.js 14+React18集成Redux-Persist后App Router路由报错求助

问题排查:Next.js 14 App Router + Redux-Toolkit + Redux-Persist 路由错误

问题描述

使用npx create-next-app@latest创建基于React 18和Next.js 14的项目,采用App Router方案,集成Redux-Toolkit和Redux-Persist做状态管理后,运行时出现Uncaught Error: invariant expected app router to be mounted错误。配置Redux-Persist前路由正常,注释组件中的useAppSelector和useAppDispatch后路由恢复正常。

错误原因

  1. RootLayout结构违反Next.js规则:将<html>和<body>包裹在PersistGate内部,Next.js要求这两个标签必须是RootLayout的直接子元素,不能被其他组件嵌套。
  2. 服务器端不兼容的persistor初始化:store.ts中全局导出persistor = persistStore(makeStore()),会在服务器端执行,而redux-persist依赖浏览器存储,导致服务器端运行异常。
  3. Redux hooks使用不当:组件中直接用useAppStore获取store并dispatch,不符合React-Redux的推荐用法,且可能触发过早的store访问。
  4. Slice导入路径不一致:RootReducer中导入的是@/slices/mySlice,但登录组件中使用的是@/redux/slices/DashboardSlice,可能导致reducer未正确注册。

修复方案

1. 修正RootLayout结构

修改src/app/layout.tsx,将Provider和PersistGate放在<body>内部,确保<html>和<body>为顶层元素:

// src/app/layout.tsx
"use client";
import { useRef } from "react";
import "./globals.css";
import Header from "./components/Header";
import { usePathname } from "next/navigation";
import { Box } from "@mui/material";
import { AppStore, makeStore } from "@/lib/store";
import { Provider } from "react-redux";
import { PersistGate } from "redux-persist/integration/react";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const pathName = usePathname();
  const storeRef = useRef<AppStore>();
  if (!storeRef.current) {
    storeRef.current = makeStore();
  }
  const persistor = storeRef.current.__persistor;

  return (
    <html lang="en">
      <body>
        <Provider store={storeRef.current}>
          <PersistGate loading={null} persistor={persistor}>
            <Box sx={{ display: "flex" }}>
              {pathName !== "/" && <Header />}
              {children}
            </Box>
          </PersistGate>
        </Provider>
      </body>
    </html>
  );
}

2. 修复store.ts的persistor导出问题

删除store.ts中全局的persistor导出,仅在客户端创建store时挂载__persistor,同时添加序列化检查忽略Redux-Persist的action:

// src/lib/store.ts
import { configureStore } from "@reduxjs/toolkit";
import RootReducer from "./RootReducer";
import {
  persistStore,
  persistReducer,
  FLUSH,
  REHYDRATE,
  PAUSE,
  PERSIST,
  PURGE,
  REGISTER,
} from "redux-persist";
import storage from "redux-persist/lib/storage";

const persistConfig = {
  key: "root",
  storage,
};

const makeConfiguredStore = (reducer = RootReducer) =>
  configureStore({
    reducer,
    middleware: (getDefaultMiddleware) =>
      getDefaultMiddleware({
        serializableCheck: {
          ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
        },
      }),
  });

export const makeStore = () => {
  const isServer = typeof window === "undefined";
  if (isServer) {
    return makeConfiguredStore();
  } else {
    const persistedReducer = persistReducer(persistConfig, RootReducer);
    const store = makeConfiguredStore(persistedReducer);
    store.__persistor = persistStore(store);
    return store;
  }
};

export type AppStore = ReturnType<typeof makeStore>;
export type RootState = ReturnType<AppStore["getState"]>;
export type AppDispatch = AppStore["dispatch"];

3. 组件中使用正确的Redux hooks

修改login.tsx,用useAppDispatch代替useAppStore:

// src/components/login.tsx
"use client";

import React from "react";
import { Container, Button, Box } from "@mui/material";
import { useRouter } from "next/navigation";
import { setDashboardData } from "@/redux/slices/DashboardSlice";
import { useAppDispatch } from "@/lib/hooks";

const LoginComp: React.FC = () => {
  const dispatch = useAppDispatch();
  const router = useRouter();
  
  const handleLoginClick = () => {
    const stateData = { message: "Dashboard page" };
    dispatch(setDashboardData(stateData));
    router.push("/dashboard");
  };
  
  return (
    <Box>
      <Container maxWidth="xs">
        <Box margin="normal">
          <Button variant="contained" onClick={handleLoginClick}>
            Login
          </Button>
        </Box>
      </Container>
    </Box>
  );
};

export default LoginComp;

4. 统一Slice导入路径

确保RootReducer包含所有用到的slice,比如添加DashboardSlice:

// src/lib/RootReducer.ts
import { combineReducers } from "@reduxjs/toolkit";
import SliceReducer from "@/slices/mySlice";
import DashboardSlice from "@/redux/slices/DashboardSlice"; // 导入DashboardSlice

const RootReducer = combineReducers({
  MySlice: SliceReducer,
  Dashboard: DashboardSlice, // 添加到根reducer
});

export default RootReducer;

验证修复

完成上述修改后,重新启动项目:

npm run dev

此时路由应该正常工作,Redux-Persist的持久化状态也能正确加载。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 01:42:24