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

Next.js 13 AppDir集成React-Redux时JSON解析错误求助

Next.js 13(appDir)集成React-Redux时JSON解析错误问题

问题描述

在启用appDir的Next.js 13项目中使用React-Redux做状态管理,启动应用时触发如下错误:

caught SyntaxError: "undefined" is not valid JSON
at JSON.parse ()
at parseModel (webpack-internal:///(:3000/app-client)/./node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js:33:15)

移除React-Redux的Provider组件后应用可正常运行,重新添加则错误复现。未在代码中直接使用JSON.parse,仅在layout.tsx中用ApolloProvider和Provider包裹子组件。已尝试更换Node.js版本、删除node_modules后重装依赖,问题仍未解决。

相关代码文件

layout.tsx

"use client"
import { Poppins } from "next/font/google";
import SiteHeader from "./(client-components)/(Header)/SiteHeader";
import ClientCommons from "./ClientCommons";
import "./globals.css";
import "@/fonts/line-awesome-1.3.0/css/line-awesome.css";
import "@/styles/index.scss";
import "rc-slider/assets/index.css";
import Footer from "@/components/Footer";
import FooterNav from "@/components/FooterNav";
import { ApolloProvider } from "@apollo/react-hooks";
import { Provider } from "react-redux";
import { store } from "../lib/store";
import client from "../lib/graphql";
const poppins = Poppins({
  subsets: ["latin"],
  display: "optional",
  weight: ["100", "300", "400", "500", "600", "700", "800"],
});

export default function RootLayout({
  children,
  params,
}: {
  children: React.ReactNode;
  params: any;
}) {
  return (
    <html lang="en" className={poppins.className}>
      <body className="bg-white text-base dark:bg-neutral-900 text-neutral-900 dark:text-neutral-200">
       <ApolloProvider client={client}>
          <Provider store={store}>
            <ClientCommons />
            <SiteHeader />
            {children}
            <FooterNav />
            <Footer />
           </Provider>
        </ApolloProvider> 
      </body>
    </html>
  );
}

propertySlice.js

import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import { fetchProperties, fetchProperty } from "./propertiesApi";

const initialState = {
  
  data: [],
  propertyData: null,
  loading: true,
  error: null,
  searchQuery: {
    location: "",
    priceMin: null,
    priceMax: null,
    propertyTypes: [],
  },
  property: {},
  selectedListingId: null,
};

const propertiesSlice = createSlice({
  name: "properties",
  initialState,
  reducers: {
    setSelectedListing(state, action) {
      state.selectedListingId = action.payload;
    },
    setProperties(state, action) {
      state.items = action.payload;
    },
    setSearchQuery(state, action) {
      state.searchQuery = action.payload;
    },
    setProperty(state, action) {
      state.property = action.payload;
    },
    setLoading(state, action) {
      state.loading = action.payload;
    },
    setError(state, action) {
      state.error = action.payload;
    },
  },

  extraReducers: (builder) => {
    builder
      .addCase(fetchProperties.pending, (state) => {
        state.loading = true;
        state.error = null;
      })
      .addCase(fetchProperties.fulfilled, (state, { payload }) => {
        state.loading = false;
        state.data = payload;
      })
      .addCase(fetchProperties.rejected, (state, { payload }) => {
        state.loading = false;
        state.error = payload;
      })
      .addCase(fetchProperty.pending, (state) => {
        state.loading = true;
        state.error = null;
      })
      .addCase(fetchProperty.fulfilled, (state, { payload }) => {
        state.loading = false;
        state.error = null;
        state.property = payload;
      })
      .addCase(fetchProperty.rejected, (state, { payload }) => {
        state.loading = false;
        state.error = payload;
      });
  },
});

export const {
  setProperty,
  setLoading,
  setError,
  setProperties,
  setSearchQuery,
  setSelectedListing,
} = propertiesSlice.actions;

export default propertiesSlice.reducer;

propertiesApi.ts

import { createAsyncThunk } from "@reduxjs/toolkit";
import { store } from "@/lib/store";
import client from "../../lib/graphql";
import { GET_AllProperties, Get_Property } from "../../graphql/queries";
import { useDispatch } from "react-redux";

export const fetchProperties = createAsyncThunk(
  "properties/fetchProperties",
  async () => {
    const { data } = await client.query({
      query: GET_AllProperties,
    });
    return data.allProperties;
  }
);

export const fetchProperty = createAsyncThunk(
  "property/fetchProperty",
  async (id) => {
    const variables = { id };
    const { data } = await client.query({
      query: Get_Property,
      variables,
    });
    return data.property;
  }
);

export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
export const useAppDispatch = () => useDispatch<AppDispatch>();

store.js

import { configureStore } from "@reduxjs/toolkit";
import propertiesSlice from '../features/properties/propertiesSlice'
import userSlice from '../features/user/userSlice'

export const store = configureStore({
  reducer: {
     properties: propertiesSlice,
     user: userSlice,
  },
});

Package.json

{
  "name": "chisfis-nextjs",
  "version": "0.2.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@apollo/react-hooks": "^4.0.0",
    "@headlessui/react": "^1.7.12",
    "@heroicons/react": "^2.0.16",
    "@mapbox/mapbox-gl-geocoder": "^5.0.1",
    "@mapbox/mapbox-sdk": "^0.15.0",
    "@netlify/plugin-nextjs": "^4.33.0",
    "@reduxjs/toolkit": "^1.9.3",
    "@tailwindcss/aspect-ratio": "^0.4.2",
    "@tailwindcss/forms": "^0.5.3",
    "@tailwindcss/line-clamp": "^0.4.2",
    "@tailwindcss/typography": "^0.5.9",
    "@types/google-map-react": "^2.1.7",
    "@types/node": "18.14.2",
    "@types/react": "18.0.28",
    "@types/react-datepicker": "^4.10.0",
    "@types/react-dom": "18.0.11",
    "client-only": "^0.0.1",
    "eslint": "8.35.0",
    "eslint-config-next": "^13.2.3",
    "framer-motion": "^10.0.1",
    "geocoding": "^1.0.0",
    "google-map-react": "^2.2.0",
    "graphql": "^16.6.0",
    "graphql-tag": "^2.12.6",
    "latest": "^0.2.0",
    "lodash": "^4.17.21",
    "mapbox-gl": "^2.13.0",
    "next": "^13.2.4-canary.9",
    "next-auth": "^4.20.1",
    "rc-slider": "^10.1.1",
    "react": "^18.2.0", 
    "react-datepicker": "^4.10.0",
    "react-dom": "^18.2.0",
    "react-hooks-global-state": "^2.1.0",
    "react-icons": "^4.8.0",
    "react-map-gl": "^7.0.21",
    "react-redux": "^8.0.0-beta.1",
    "react-swipeable": "^7.0.0",
    "react-use": "^17.4.0",
    "react-use-keypress": "^1.3.1",
    "react-whatsapp": "^0.3.0",
    "redux": "^4.2.1",
    "sass": "^1.58.3",
    "server-only": "^0.0.1",
    "sharp": "^0.32.0",
    "typescript": "4.9.5"
  },
  "devDependencies": {
    "@types/mapbox__mapbox-gl-geocoder": "^4.7.3",
    "autoprefixer": "^10.4.13",
    "postcss": "^8.4.21",
    "tailwindcss": "^3.2.7",
    "webpack": "^5.78.0"
  }
}

解决方案

1. 升级React-Redux到稳定版

当前使用的react-redux是测试版8.0.0-beta.1,和Next.js 13 App Router存在兼容性风险。执行以下命令升级:

npm install react-redux@latest

2. 修复Redux状态序列化问题

Next.js App Router在服务器/客户端组件交互时会序列化上下文数据,若Redux状态包含不可序列化的值(如函数、循环引用、Apollo Client缓存对象),会触发JSON解析错误:

  • 检查userSlice的初始状态和reducer逻辑,确保所有状态值均为可序列化类型(字符串、数字、数组、普通对象)
  • 确保AsyncThunk返回的payload是纯JSON数据,避免传递Apollo Client的原始响应对象

3. 调整Provider的包裹方式

RootLayout作为根布局组件,直接添加"use client"可能导致上下文传递异常。将Provider移到单独的客户端组件中:
新建Providers.tsx:

"use client"
import { ApolloProvider } from "@apollo/react-hooks";
import { Provider } from "react-redux";
import { store } from "../lib/store";
import client from "../lib/graphql";

export default function Providers({ children }: { children: React.ReactNode }) {
  return (
    <ApolloProvider client={client}>
      <Provider store={store}>
        {children}
      </Provider>
    </ApolloProvider>
  )
}

修改layout.tsx,移除"use client"并引入Providers组件:

import { Poppins } from "next/font/google";
import SiteHeader from "./(client-components)/(Header)/SiteHeader";
import ClientCommons from "./ClientCommons";
import "./globals.css";
import "@/fonts/line-awesome-1.3.0/css/line-awesome.css";
import "@/styles/index.scss";
import "rc-slider/assets/index.css";
import Footer from "@/components/Footer";
import FooterNav from "@/components/FooterNav";
import Providers from "./Providers";

const poppins = Poppins({
  subsets: ["latin"],
  display: "optional",
  weight: ["100", "300", "400", "500", "600", "700", "800"],
});

export default function RootLayout({
  children,
  params,
}: {
  children: React.ReactNode;
  params: any;
}) {
  return (
    <html lang="en" className={poppins.className}>
      <body className="bg-white text-base dark:bg-neutral-900 text-neutral-900 dark:text-neutral-200">
        <Providers>
          <ClientCommons />
          <SiteHeader />
          {children}
          <FooterNav />
          <Footer />
        </Providers>
      </body>
    </html>
  );
}

4. 完善AsyncThunk错误处理

当请求失败时,确保返回可序列化的错误信息,避免undefined进入Redux状态:

export const fetchProperties = createAsyncThunk(
  "properties/fetchProperties",
  async (_, { rejectWithValue }) => {
    try {
      const { data } = await client.query({
        query: GET_AllProperties,
      });
      return data.allProperties;
    } catch (err) {
      return rejectWithValue(err.message || "获取房源列表失败");
    }
  }
);

export const fetchProperty = createAsyncThunk(
  "property/fetchProperty",
  async (id, { rejectWithValue }) => {
    try {
      const variables = { id };
      const { data } = await client.query({
        query: Get_Property,
        variables,
      });
      return data.property;
    } catch (err) {
      return rejectWithValue(err.message || "获取房源详情失败");
    }
  }
);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 20:29:55