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

Next.js生产环境加载商品页报n.map未定义客户端异常

问题现象

尝试加载渲染从外部API拉取的商品信息页面时,出现报错:Next.js Application error: a client-side exception has occurred (see the browser console for more information)。

  • 错误仅在生产环境触发,本地开发环境运行一切正常
  • 浏览器控制台可见具体错误信息:

TypeError: undefined is not a function (near '...n.map...')

  • 本地执行Next Build可正常完成生产构建,执行Next Start运行本地构建的生产产物时无控制台或其他位置报错,仅将代码部署到线上生产环境后才会触发该错误。
相关业务代码

商品列表渲染组件

import type { NextPage } from 'next'
import React, { useState, useEffect, useContext} from 'react';
import styles from '../styles/Home.module.css'
import DepopIcon from './icons/depop.svg'
import { Image, Main, Heading, Paragraph, Nav, Icons, Header, Grommet, Box, Button, Grid, Text, Footer, Anchor } from 'grommet';
import {
  Instagram,
  Shop,
  Cart
} from 'grommet-icons';
import { useRouter } from 'next/router';

export default function ProductsList() {
    const router = useRouter();
    // 初始化商品状态变量
    const [products, setProducts] = useState([]);
    const [prodImg, setProdImg] = useState([]);


    function handleProdClick(id){
        router.push({ pathname: "/product", query: { id: id } })
    }

    const getProducts = async () => {
         const response = await fetch("/api/getproducts");
         var data = await response.json();
         data = JSON.parse(data);
         await setProducts(data);
    }

    useEffect(() => {
            getProducts();
        }, [])

    // 构建网格布局的areas配置
    const areaVar = "area";
    const cols = 3;
    var r = 0;
    var c = 0;
    var areas = []
    for (let x = 1; x < products.length+1; x++){
        const name = areaVar+x.toString()
        areas.push({ name: name, start: [r,c], end: [r,c]  })
        r += 1
        if (x % 3 == 0){
            r = 0
            c += 1
        }
    }

    console.log(products);

    // 生成网格内的商品项组件
    var productObjs = [];
    if (typeof products != "undefined" && products != null){
        productObjs = products.map((product, index) => {
            return(
            <Box
              key={product._id}
              gridArea= {areas[index].name}
              background="#003311"
              direction="column"
              pad="none"
              align="center"
              onClick={() => {handleProdClick(product._id)}}
            >
                <Image
                  src={product.frontImage} fill={true} fit="cover"
                  onMouseOver={e => (e.currentTarget.src = product.backImage)}
                  onMouseOut={e => (e.currentTarget.src = product.frontImage)}
                />
                <Box
                  direction="row"
                  pad="xsmall"
                  align="center"
                  gap="large"
                  justify="between"
                  animation="fadeIn"
                >
                  <Text color="#fff" size="medium">{product.name}</Text>
                  <Text color="#fff" size="medium">{product.color}</Text>
                  <Text color="#fff" size="medium">{product.price} USD</Text>
                  <Text color="#fff" size="medium">{product.size}</Text>
                </Box>
           </Box>
            );
        });
    }

    const rows = []
    for (let i = 0; i < r; i++){
        rows.push('large')
    }
    return (
        <Grid
          rows={rows}
          columns={['flex', 'flex', 'flex']}
          gap="none"
          areas={areas}
        >
          {productObjs}
        </Grid>
    );
}

商品页面代码(引用上述组件)

import type { NextPage } from 'next'
import React, { useState, useContext, useEffect} from 'react';
import { Paragraph, Main, Nav, Icons, Header, Grommet, Box, Button, Grid, Text, Footer, Anchor } from 'grommet';
import ContactHeaderUnit from '../components/ContactHeaderUnit.tsx'
import FooterUnit from '../components/FooterUnit.tsx'
import ProductsList from '../components/ProductsList.tsx'

const Shop: NextPage = () => {

      return (
          <Grommet
              full={true}
              background="#fffced"
              theme={{
                  global: { font: {
                              family: "Alice"
                            }},
                paragraph: {
                    font: {
                      family: 'Arial',
                    },
                },
                label: {
                    font: {
                      family: 'Arial',
                    },
                  },
              }}
          >
            <ContactHeaderUnit/>
            <ProductsList/>
            <FooterUnit/>
          </Grommet>
      )
}

export default Shop;
问题根因
  1. 接口返回值解析逻辑错误:getProducts方法中先调用response.json()将响应体解析为JavaScript对象,之后又额外执行了一次JSON.parse(data)。线上接口返回标准JSON格式响应时,第一次解析后得到的已经是目标数组/对象,对非字符串类型的值执行JSON.parse会得到undefined,最终给products状态赋值为undefined,后续调用.map()方法时直接触发报错。本地与线上表现不一致,通常是本地开发环境的接口代理、mock数据或接口返回格式与线上存在细微差异导致。
  2. 类型校验逻辑不严谨:现有代码仅判断products不是undefined和null,未校验其是否为数组类型,一旦接口返回对象、字符串等非数组值,依然会触发.map is not a function错误。
  3. 状态更新写法不规范:setProducts是React同步状态更新函数,本身无返回值,前面添加await无任何实际作用。
  4. 导入写法不规范:组件导入时添加了.tsx后缀,在不同生产构建环境下可能出现路径解析异常。
修复方案
  1. 修正接口数据解析逻辑,移除多余的JSON.parse调用,添加数组类型校验和异常兜底:
const getProducts = async () => {
  try {
    const response = await fetch("/api/getproducts");
    const data = await response.json();
    // 校验返回值是否为数组,异常场景兜底为空数组
    setProducts(Array.isArray(data) ? data : []);
  } catch (err) {
    console.error("获取商品列表失败:", err);
    // 请求失败时兜底为空数组,避免渲染报错
    setProducts([]);
  }
}
  1. 修正组件导入写法,移除导入路径中的.tsx后缀,避免构建环境路径解析问题:
import ContactHeaderUnit from '../components/ContactHeaderUnit'
import FooterUnit from '../components/FooterUnit'
import ProductsList from '../components/ProductsList'
  1. 可选优化:清理代码中未使用的导入项(未使用的NextPage类型、DepopIcon、prodImg状态、未用到的grommet组件、grommet-icons图标等),减少生产包体积;添加数据加载态,数据未返回时渲染loading提示,提升用户体验。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 17:06:27