本文详解 next.js 中动态路由 404 的常见误区,指出混用 pages router 与 app router 是主因,并提供从目录结构、文件路径到参数获取的完整迁移指南。
本文详解 next.js 中动态路由 404 的常见误区,指出混用 pages router 与 app router 是主因,并提供从目录结构、文件路径到参数获取的完整迁移指南。
你在开发食谱应用时遇到 /recipes/[recipeId] 页面始终显示 “Page Not Found”,并非代码逻辑错误,而是架构层级的根本性冲突:你正在 pages/ 目录下尝试使用 App Router 的 API(如 generateStaticParams),而该 API 仅在 /app 目录中生效。
❌ 错误根源:Router 混用
- pages/recipes/[recipeId].tsx 属于 Pages Router(已废弃但仍支持),它不识别 generateStaticParams、async 组件默认导出等 App Router 特性;
- generateStaticParams() 是 App Router 专属的静态生成钩子,放在 pages/ 下会被完全忽略,导致 Next.js 无法预生成或识别动态路径;
- 同时,useRouter() 在 Pages Router 中可用,但 params 作为组件 props 传入的方式({ params }: { params: { recipeId: string } })是 App Router 的约定,Pages Router 中需通过 router.query.recipeId 手动读取。
✅ 正确解法:统一迁移到 App Router
按 Next.js 官方推荐路径重构项目结构:
开箱即用的技能链路由引擎。13 条预定义链覆盖搜索、开发、审查、MLOps、法律、创意等场景,三层路由架构(触发词→SAD反馈→DAG编排),recall@10=96.97%。配置驱动(chains.yaml),零代码扩展。pip install skill-weave-chains 一键安装。
| Pages Router 路径 | App Router 对应路径 | 说明 |
|---|---|---|
| pages/_document.tsx | app/layout.tsx | 替换为根布局组件,返回 {children} |
| pages/index.tsx | app/page.tsx | 首页组件,直接导出默认函数组件 |
| pages/recipes/[recipeId].tsx | app/recipes/[recipeId]/page.tsx | 关键修复点:动态段必须置于 app/ 下的子目录中,且文件名固定为 page.tsx |
✅ 修正后的 app/recipes/[recipeId]/page.tsx
// app/recipes/[recipeId]/page.tsx
import { notFound } from 'next/navigation';
import Recipe from '@/components/recipe'; // 使用绝对路径别名(需在 tsconfig.json 中配置)
// ✅ App Router:静态生成所有已知食谱
export async function generateStaticParams() {
return [
{ recipeId: 'spaghettiCarbonara' },
{ recipeId: 'lasagna' },
{ recipeId: 'pizza' },
];
}
// ✅ App Router:服务端组件,params 自动注入
export default async function RecipePage({
params
}: {
params: { recipeId: string }
}) {
const { recipeId } = params;
// ✅ 加载数据(服务端执行,避免 window 报错)
let recipeData;
try {
const res = await fetch(
`https://your-api.com/recipes/${recipeId}.json`,
{ cache: 'force-cache' }
);
if (!res.ok) throw new Error('Recipe not found');
recipeData = await res.json();
} catch (err) {
console.error(err);
notFound(); // 触发 404 页面
}
return (
<div classname="max-w-3xl mx-auto p-4">
<h1 classname="text-2xl font-bold mb-4">{recipeData.title}</h1>
<recipe recipe="{recipeData}"></recipe>
</div>
);
}
? 重要提醒:
- 删除 window.alert() 和 console.log() 等浏览器专属操作——App Router 页面组件默认在服务端渲染(SSR),无 window 对象;
- JSON 数据建议通过 fetch 或 getServerSideProps(Pages Router)加载,而非 require()(Node.js 仅限服务端,且不支持动态路径);
- 若坚持使用 Pages Router,请移除 generateStaticParams,改用 getStaticPaths + getStaticProps,并从 router.query 获取 ID。
? 总结:三步避坑
- 确认 Router 类型:查看项目是否存在 /app 目录 —— 有则用 App Router,无则用 Pages Router,绝不混用;
- 路径即路由:App Router 中,/app/recipes/[id]/page.tsx → 自动匹配 /recipes/:id;
- 数据加载守规矩:服务端组件用 fetch,客户端组件用 useEffect + useRouter,避免跨环境错误。
遵循此结构,你的食谱动态页将立即生效,且获得更好的性能、SEO 与增量静态再生(ISR)支持。










