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

Next.js 14: getServerSideProps序列化products时出现undefined值错误

Next.js 14.2.3中getServerSideProps获取Stripe产品的序列化错误解决

问题描述

使用Next.js 14.2.3,在getServerSideProps中通过@stripe/firestore-stripe-payments的getProducts函数从Firestore获取产品列表时,触发序列化错误:

Server Error

Error: Error serializing .products returned from getServerSideProps in "/".

Reason: undefined cannot be serialized as JSON. Please use null or omit this value.

相关代码

import Head from "next/head";
import Header from "@/components/Header";
import Banner from "@/components/Banner";
import { Movie } from "@/typings";
import requests from "@/utils/requests";
import Row from "@/components/Row";
import useAuth from "@/hooks/useAuth";
import { useRecoilValue } from "recoil";
import { modalState } from "@/atoms/moduleAtom";
import Modal from "@/components/Modal";
import Plans from "@/components/Plans";
import { Product, getProducts } from "@stripe/firestore-stripe-payments";
import payments from "@/lib/stripe";

interface Props {
  netflixOriginals: Movie[];
  trendingNow: Movie[];
  topRated: Movie[];
  actionMovies: Movie[];
  comedyMovies: Movie[];
  horrorMovies: Movie[];
  romanceMovies: Movie[];
  documentaries: Movie[];
  products: Product[];
}

const Home = ({
  netflixOriginals,
  actionMovies,
  comedyMovies,
  documentaries,
  horrorMovies,
  romanceMovies,
  topRated,
  trendingNow,
  products,
}: Props) => {
  console.log(products);
  const { logout, loading } = useAuth();
  const showModal = useRecoilValue(modalState);
  const subscription = false;

  if (loading || subscription === null) return null;

  if (!subscription) return <Plans />;

  return (
    <div
      // 打开模态框时禁止滚动
      className={`relative h-screen bg-gradient-to-b lg:h-[140vh] ${showModal && "!h-screen overflow-hidden"}`}
    >
      <Head>
        <title>Home - Netflix</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <Header />
      <main className="relative pl-4 pb-24 lg:space-y-24 lg:pl-16">
        <Banner netflixOriginals={netflixOriginals} />
        <section className="md:space-y-24">
          <Row title="Trending Now" movies={trendingNow} />
          <Row title="Top Rated" movies={topRated} />
          <Row title="Action Thrillers" movies={actionMovies} />
          {/* My List Component */}
          <Row title="Comedies" movies={comedyMovies} />
          <Row title="Scary Movies" movies={horrorMovies} />
          <Row title="Romance Movies" movies={romanceMovies} />
          <Row title="Documentaries" movies={documentaries} />
        </section>
      </main>
      {showModal && <Modal />}
    </div>
  );
};

export const getServerSideProps = async () => {
  const products = await getProducts(payments, {
    includePrices: true,
    activeOnly: true,
  }).then((res) => res).catch((error) => console.log(error.message));

  const [
    netflixOriginals,
    trendingNow,
    topRated,
    actionMovies,
    comedyMovies,
    horrorMovies,
    romanceMovies,
    documentaries,
  ] = await Promise.all([
    fetch(requests.fetchNetflixOriginals).then((res) => res.json()),
    fetch(requests.fetchTrending).then((res) => res.json()),
    fetch(requests.fetchTopRated).then((res) => res.json()),
    fetch(requests.fetchActionMovies).then((res) => res.json()),
    fetch(requests.fetchComedyMovies).then((res) => res.json()),
    fetch(requests.fetchHorrorMovies).then((res) => res.json()),
    fetch(requests.fetchRomanceMovies).then((res) => res.json()),
    fetch(requests.fetchDocumentaries).then((res) => res.json()),
  ]);

  return {
    props: {
      netflixOriginals: netflixOriginals.results,
      trendingNow: trendingNow.results,
      topRated: topRated.results,
      actionMovies: actionMovies.results,
      comedyMovies: comedyMovies.results,
      horrorMovies: horrorMovies.results,
      romanceMovies: romanceMovies.results,
      documentaries: documentaries.results,
      products,
    },
  };
};

export default Home;

已尝试的解决方法

  • 为getProducts调用添加错误处理以记录问题
  • 通过JSON.stringify和JSON.parse将products数组中的undefined值转为null
  • 多次重启Firebase、创建新数据库并设置新的Stripe产品
  • 验证Firebase数据库中产品集合存在
  • 确认Firebase已正确关联应用(注册用户可成功添加到数据库)

解决方案

1. 处理getProducts的错误返回,避免undefined

当前catch块仅打印错误但返回undefined,导致products变量为undefined,直接序列化会报错。修改错误处理逻辑,返回空数组:

const products = await getProducts(payments, {
  includePrices: true,
  activeOnly: true,
}).catch((error) => {
  console.log(error.message);
  return []; // 错误时返回空数组,避免undefined
});

2. 递归清理Product对象中的深层undefined值

getProducts返回的Product对象可能存在深层的undefined属性,JSON.stringify/parse无法完全处理嵌套结构,需要手动递归清理:

// 递归将对象中的undefined转为null
const cleanUndefined = (obj) => {
  if (Array.isArray(obj)) {
    return obj.map(item => cleanUndefined(item));
  }
  if (obj && typeof obj === 'object') {
    return Object.fromEntries(
      Object.entries(obj).map(([key, value]) => [
        key,
        value === undefined ? null : cleanUndefined(value)
      ])
    );
  }
  return obj === undefined ? null : obj;
};

// 在getServerSideProps中使用
const rawProducts = await getProducts(payments, {
  includePrices: true,
  activeOnly: true,
}).catch((error) => {
  console.log(error.message);
  return [];
});
const products = cleanUndefined(rawProducts);

3. 确保props返回值的序列化安全性

在返回props时,兜底确保products始终为数组类型,避免意外的undefined:

return {
  props: {
    // ...其他props
    products: products || [], // 确保永远是数组,不会是undefined
  },
};

最终修改后的getServerSideProps示例

export const getServerSideProps = async () => {
  const cleanUndefined = (obj) => {
    if (Array.isArray(obj)) {
      return obj.map(item => cleanUndefined(item));
    }
    if (obj && typeof obj === 'object') {
      return Object.fromEntries(
        Object.entries(obj).map(([key, value]) => [
          key,
          value === undefined ? null : cleanUndefined(value)
        ])
      );
    }
    return obj === undefined ? null : obj;
  };

  const rawProducts = await getProducts(payments, {
    includePrices: true,
    activeOnly: true,
  }).catch((error) => {
    console.log(error.message);
    return [];
  });
  const products = cleanUndefined(rawProducts);

  const [
    netflixOriginals,
    trendingNow,
    topRated,
    actionMovies,
    comedyMovies,
    horrorMovies,
    romanceMovies,
    documentaries,
  ] = await Promise.all([
    fetch(requests.fetchNetflixOriginals).then((res) => res.json()),
    fetch(requests.fetchTrending).then((res) => res.json()),
    fetch(requests.fetchTopRated).then((res) => res.json()),
    fetch(requests.fetchActionMovies).then((res) => res.json()),
    fetch(requests.fetchComedyMovies).then((res) => res.json()),
    fetch(requests.fetchHorrorMovies).then((res) => res.json()),
    fetch(requests.fetchRomanceMovies).then((res) => res.json()),
    fetch(requests.fetchDocumentaries).then((res) => res.json()),
  ]);

  return {
    props: {
      netflixOriginals: netflixOriginals.results,
      trendingNow: trendingNow.results,
      topRated: topRated.results,
      actionMovies: actionMovies.results,
      comedyMovies: comedyMovies.results,
      horrorMovies: horrorMovies.results,
      romanceMovies: romanceMovies.results,
      documentaries: documentaries.results,
      products,
    },
  };
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 18:04:52