Next.js 13 App Router中带use client的动态路由静态导出失败
本地运行动态路由页面(如localhost:3000/tickets/2)正常,但执行npm run build导出静态站点时出现以下错误:
Error occurred prerendering page "/tickets/[id]". Read more: https://nextjs.org/docs/messages/prerender-error TypeError: Cannot read properties of undefined (reading 'id')
已知条件:
- 页面顶部添加了
"use client",因为需要使用useState钩子; - 移除
"use client"和状态管理代码后,npm run build可顺利导出静态页面(Next.js会为每个ticket id生成如/tickets/1、/tickets/2的静态页面)。
相关配置代码:
next.config.js
module.exports = { output: "export", images: { unoptimized: true, } };
app/tickets/[id]/page.js
"use client"; import { useState } from "react"; import Select from "components/Select"; const TICKETS = [ { id: 0, status: "open" }, { id: 1, status: "open" }, { id: 2, status: "resolved" }, { id: 3, status: "canceled" }, { id: 4, status: "resolved" } ] export async function generateStaticParams() { return TICKETS.map((ticket) => ({ id: `${ticket.id}`, })); } export default function Ticket({ params }) { const { id } = params; const ticket = TICKETS[id]; const [status, setStatus] = useState(ticket.status); return ( <div> <h1>{ticket.id}</h1> <Select onValueChange={(e) => { setStatus(e); }} value={status} /> </div> ); }
generateStaticParams是Next.js的服务器端专属函数,用于静态导出时生成路由参数。当页面添加"use client"指令后,整个组件会被标记为客户端组件,服务器端不会执行客户端组件中的generateStaticParams函数,导致预渲染时无法获取params,进而出现Cannot read properties of undefined (reading 'id')错误。
本地开发环境下,Next.js会在服务器端兜底处理路由参数传递,所以不会暴露这个问题;但静态导出时,预渲染逻辑严格依赖服务器端的参数生成逻辑,客户端组件中的generateStaticParams完全失效。
将服务器端逻辑(参数生成、数据获取)与客户端逻辑(状态管理、交互)拆分,让generateStaticParams保留在服务器组件中:
步骤1:拆分服务器组件(page.js)
修改app/tickets/[id]/page.js,移除"use client",仅保留参数生成和数据传递逻辑:
import TicketClient from "./TicketClient"; const TICKETS = [ { id: 0, status: "open" }, { id: 1, status: "open" }, { id: 2, status: "resolved" }, { id: 3, status: "canceled" }, { id: 4, status: "resolved" } ]; export async function generateStaticParams() { return TICKETS.map((ticket) => ({ id: `${ticket.id}`, })); } export default function Ticket({ params }) { const { id } = params; const ticket = TICKETS[id]; // 将获取到的ticket数据传递给客户端组件 return <TicketClient ticket={ticket} />; }
步骤2:创建客户端组件(TicketClient.js)
在同目录下创建TicketClient.js,添加"use client"和状态管理逻辑:
"use client"; import { useState } from "react"; import Select from "components/Select"; export default function TicketClient({ ticket }) { const [status, setStatus] = useState(ticket.status); return ( <div> <h1>{ticket.id}</h1> <Select onValueChange={(e) => { setStatus(e); }} value={status} /> </div> ); }
验证
执行npm run build,静态导出将正常生成所有tickets/[id]对应的静态页面,同时客户端组件的状态管理逻辑也能正常工作。
内容的提问来源于stack exchange,提问作者sea_monster

