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

