Next.js/TypeScript中generateStaticParams()失效问题求助
Next.js 动态路由静态预渲染失效及500错误修复
问题现象
- 执行
npm run build时仅生成3个静态页面(实际应生成所有产品对应的详情页) - 部署到Vercel后,点击产品卡片跳转详情页触发500内部服务器错误
- 功能此前正常,新增TypeScript类型定义后出现异常
相关代码排查
动态路由页面(app/services/[id]/page.tsx)
import Image from "next/image" export async function generateStaticParams() { const res = await fetch('https://dummyjson.com/products') const products = await res.json() const paths = products.products.map((product: {id: number}) => ({ id: `${product.id}`, })) return paths } async function getProduct(id: number) { const res = await fetch(`https://dummyjson.com/products/${id}`) const data = await res.json() return data } export default async function Service({ params }: { params: {id: number} }) { const product = await getProduct(params.id) return ( <div style={{display: "flex", flexDirection: "column"}}> <h1>{product.title}</h1> <Image src={product.thumbnail} alt="asd" width={400} height={400}/> <span>In stock: {product.stock}</span> </div> ) }
父页面(app/services/page.tsx)
"use client"; import '@/styles/BlogServices.css' import ServiceSection from '@/components/ServiceSection.tsx' import React from 'react' export default function Services() { interface Product { id: number, title: string, description: string, price: number, image: string, // 注意:dummyjson返回的图片字段是thumbnail,不是image category: string } const [searchedService, setSearchedService] = React.useState<Product[]>([]) const [defaultService, setDefaultService] = React.useState<Product[]>([]) React.useEffect(() => { fetch('https://dummyjson.com/products') .then(res => res.json()) .then(res => { setSearchedService(res.products) setDefaultService(res.products) }) },[]) function handleSearch (value:string) { setSearchedService(defaultService.filter(item => item.title.toLowerCase().includes(value.toLowerCase()))) } return ( <div className="services-page-container"> <div className="blog-service-page-title-container parent-flex-column-center dark:bg-slate-900"> <span className="blog-service-page-short-title">SERVICES</span> <h1 className="blog-service-page-title font-bold">Choose and book service with us</h1> <p className="blog-service-page-desc text-page-subtitle text-xl dark:text-slate-400">Subscribe to learn about new product features, the latest in technology, solutions, and updates.</p> </div> <ServiceSection servicePage descStyle='services-section-desc' handleSearch={handleSearch} searchedService={searchedService} defaultService={defaultService} /> </div> ) }
产品卡片组件(components/ServiceCard.tsx)
"use client"; import servicearrow from '../public/assets/servicearrow.svg' import '../styles/Card.css' import Image, { StaticImageData } from 'next/image' import { useRouter } from 'next/navigation' interface ServiceCard { id?: number; title: string; desc: string; date?: string; img: string | StaticImageData; imgStyle: string; contStyle: string; descStyle: string; servicepage?: boolean; handleClick?: () => void; } export default function ServiceCard(props: ServiceCard) { const router = useRouter() function handleClick() { props.servicepage && router.push(`/services/${props.id}`) } return ( <div className={`card-container ${props.contStyle} dark:bg-slate-600`} onClick={handleClick}> <Image src={props.img} className={`card-img ${props.imgStyle}`} width={300} height={300} alt=''/> <span className='card-date'>{props.date}</span> <h3 className='text-xl font-bold mb-5 dark:text-slate-200'>{props.title}</h3> <p className={`card-description ${props.descStyle} dark:text-slate-50`}>{props.desc}</p> <span className='text-service-card-orange flex mt-auto dark:text-orange-700'>View full information <Image className='ml-6' src={servicearrow} alt="" /></span> </div> ) }
问题根源
- 路由参数类型不匹配:Next.js路由参数始终是字符串类型,但动态路由页面中把
params.id定义为number,同时getProduct函数接收number类型参数,导致类型冲突,静态生成时无法正确遍历所有路径,运行时也会因类型转换异常触发500错误。 - 接口字段定义错误:父页面
Product接口中定义的image字段与dummyjson返回的thumbnail字段不符,可能导致卡片图片加载异常,间接影响路由跳转逻辑。 - 可选参数未做校验:
ServiceCard中id是可选属性,跳转时未判断id是否存在,可能生成无效路由。
修复步骤
1. 修正动态路由页面的类型定义
import Image from "next/image" // 定义明确的产品类型 interface Product { id: number; title: string; thumbnail: string; stock: number; } export async function generateStaticParams() { // 增加错误处理,避免fetch失败导致生成空路径 try { const res = await fetch('https://dummyjson.com/products') if (!res.ok) throw new Error('Failed to fetch product list') const data = await res.json() const paths = data.products.map((product: Product) => ({ id: product.id.toString(), })) return paths } catch (err) { console.error('Error generating static params:', err) return [] } } // 修改getProduct参数为string,匹配路由参数类型 async function getProduct(id: string) { const res = await fetch(`https://dummyjson.com/products/${id}`) const data = await res.json() return data as Product } // 修正params类型为string export default async function Service({ params }: { params: {id: string} }) { const product = await getProduct(params.id) return ( <div style={{display: "flex", flexDirection: "column"}}> <h1>{product.title}</h1> <Image src={product.thumbnail} alt={product.title} width={400} height={400}/> <span>In stock: {product.stock}</span> </div> ) }
2. 修正父页面的Product接口
// 修正字段名,匹配dummyjson返回结构 interface Product { id: number; title: string; description: string; price: number; thumbnail: string; // 替换image为thumbnail category: string; }
3. 完善卡片组件的路由跳转逻辑
function handleClick() { // 增加id存在性校验,避免无效跳转 if (props.servicepage && props.id !== undefined) { router.push(`/services/${props.id}`) } }
4. 验证修复效果
- 执行
npm run build,查看控制台输出的静态页面数量是否与产品总数一致 - 本地启动生产环境服务(
npm start),测试跳转是否正常 - 重新部署到Vercel,确认500错误消失
内容的提问来源于stack exchange,提问作者Ucha
相关产品推荐
相关产品推荐

