Skip to content

Next.js - 动态 API 路由

动态路由(Dynamic routes)允许你根据动态数据创建页面,例如博客文章、产品详情或用户个人资料等,这些页面的 URL 结构取决于参数。

在 App Router 中,动态段(dynamic segments)通过在文件夹名称中使用方括号 [] 来定义。例如,app/posts/[slug]/page.tsx 会创建诸如 /posts/my-first-post、/posts/learning-nextjs 等路由。

让我们构建一个简单的博客,其中每篇文章都基于唯一的 ‘slug’ 拥有自己的页面。

步骤 1:定义数据源

创建一个工具文件来模拟获取博客文章数据。创建 lib/posts.ts(如果项目根目录中没有 lib 目录,请创建它):

lib/posts.ts
export interface Post {
slug: string;
title: string;
content: string;
date: string;
}
// 模拟数据库或 API 调用
const posts: Post[] = [
{
slug: 'getting-started-with-nextjs',
title: 'Getting Started with Next.js',
content: 'Next.js is a React framework for production...',
date: '2024-01-10',
},
{
slug: 'dynamic-routing-explained',
title: 'Dynamic Routing Explained',
content: 'Learn how dynamic routes work in the App Router...',
date: '2024-01-15',
},
];
// 获取所有可能 slug 的函数(用于静态生成)
export async function getAllPostSlugs(): Promise<{ slug: string }[]> {
// 在实际应用中,从 CMS 或数据库中获取此数据
return posts.map(post => ({ slug: post.slug }));
}
// 按 slug 获取文章数据的函数
export async function getPostData(slug: string): Promise<Post | null> {
// 在实际应用中,从 CMS 或数据库中获取此数据
const post = posts.find(p => p.slug === slug);
return post || null;
}

步骤 2:创建动态页面组件

创建文件 app/posts/[slug]/page.tsx。文件夹名称 [slug] 表示 slug 是一个动态参数。

app/posts/[slug]/page.tsx
import { getPostData, getAllPostSlugs } from '@/lib/posts'; // 使用路径别名 '@'
import { notFound } from 'next/navigation';
import { Metadata } from 'next';
interface PostPageProps {
params: {
slug: string; // 动态段的值
};
}
// 动态生成元数据的函数
export async function generateMetadata({ params }: PostPageProps): Promise<Metadata> {
const post = await getPostData(params.slug);
if (!post) {
return { title: 'Post Not Found' };
}
return { title: post.title };
}
// 在构建时生成静态路径的函数
export async function generateStaticParams() {
const slugs = await getAllPostSlugs();
// 返回一个数组,例如:[{ slug: 'post-1' }, { slug: 'post-2' }]
return slugs;
}
// 页面组件
export default async function PostPage({ params }: PostPageProps) {
const post = await getPostData(params.slug);
// 处理找不到文章数据的情况
if (!post) {
notFound(); // 渲染最近的 not-found.tsx 页面或默认的 404 页面
}
return (
<article>
<h1>{post.title}</h1>
<p><time dateTime={post.date}>{post.date}</time></p>
<div>{post.content}</div>
{/* 如果需要,添加一个返回链接 */}
{/* <Link href="/posts">Back to Posts List</Link> */}
</article>
);
}
  • **[slug] 文件夹:** 定义了一个名为 slug 的动态路由段(dynamic route segment)。
  • **paramsProp:** 页面组件接收一个paramsprop,其中包含当前请求的动态段的实际值(例如{ slug: ‘getting-started-with-nextjs’ }`)。
  • **generateStaticParams():** (可选,但建议用于静态生成)此函数告诉 Next.js 在构建时存在哪些可能的 slug 值。Next.js 将为该函数返回的每个路径预渲染一个静态 HTML 页面。如果用户请求了此处未生成的路径,Next.js 可能会尝试按需生成或显示 404,具体取决于配置(layout.js或page.js中的dynamicParams选项,默认为true`)。
  • **数据获取:** PostPage组件是一个服务器组件(Server Component),因此它直接使用await getPostData(params.slug)获取数据。无需像 Pages Router 中那样使用getStaticProps或getServerSideProps`。
  • **notFound():** 来自 next/navigation` 的一个辅助函数,用于优雅地处理请求的 slug 数据不存在的情况,渲染 404 页面。
  • **generateMetadata()`:** (可选)允许基于获取的数据动态生成页面元数据(如标题、描述)。

运行开发服务器:

npm run dev
  1. 在浏览器中打开 http://localhost:3000/posts/getting-started-with-nextjs。你应该能看到第一篇博客文章的内容。

  2. 打开 http://localhost:3000/posts/dynamic-routing-explained。你应该能看到第二篇博客文章的内容。

  3. 尝试导航到不存在的 slug,例如 http://localhost:3000/posts/invalid-slug。你应该会看到一个 404 未找到页面(如果你创建了自定义的 not-found.tsx,则显示该页面,否则显示默认的 Next.js 404 页面)。