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后路由恢复正常。
错误原因
- RootLayout结构违反Next.js规则:将
<html>和<body>包裹在PersistGate内部,Next.js要求这两个标签必须是RootLayout的直接子元素,不能被其他组件嵌套。 - 服务器端不兼容的persistor初始化:
store.ts中全局导出persistor = persistStore(makeStore()),会在服务器端执行,而redux-persist依赖浏览器存储,导致服务器端运行异常。 - Redux hooks使用不当:组件中直接用
useAppStore获取store并dispatch,不符合React-Redux的推荐用法,且可能触发过早的store访问。 - 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
相关产品推荐
相关产品推荐

