Next.js 路由系统App Router
本文概括:
-
文件即路由规则 :文件夹结构决定 URL,对应的特殊文件有
layout.tsx、loading.tsx、error.tsx、not-found.tsx、template.tsx。 -
路由段类型 :支持动态路由、多级动态路由、Catch-all(
[...slug]、[[...slug]])等。 -
参数获取方式:
- 动态段参数:服务端用
params,客户端用useParams。 - 查询参数:服务端用
searchParams,客户端用useSearchParams。
- 动态段参数:服务端用
-
路由导航:
- 声明式:
<Link />。 - 服务端:
redirect()。 - 客户端:
useRouter.push()。
- 声明式:
-
路由控制:
- 404 页面:
not-found.tsx或notFound()。 - 路由组
(group)和私有_路由。
- 404 页面:
-
布局文件 :
layout.tsx可嵌套,用于 UI 框架复用。 -
Metadata(SEO) :
- 静态:
export const metadata。 - 动态:
generateMetadata()。 - 继承 & 覆盖规则,SEO 最佳实践。
- 静态:
-
其他 API :
usePathname、useParams对比,适用场景。
服务端&客户端 API对照表:
| 功能 | 服务端可用 | 客户端可用 |
|---|---|---|
| 获取动态参数 | params |
useParams |
| 获取查询参数 | searchParams |
useSearchParams |
| 路由跳转 | redirect() |
useRouter.push() / router.replace() |
| 获取 pathname | ❌ | usePathname() |
| 404 控制 | notFound() |
❌ |
| Metadata | metadata / generateMetadata() |
❌ |
| 错误边界 | error.tsx(需 "use client") |
error.tsx 内逻辑 |
| Loading 状态 | loading.tsx |
❌ |
| 布局 | layout.tsx |
❌ |
| 声明式导航 | Link(通用) |
Link(通用) |
文件即路由规则
-
规则 :
app/segment/page.tsx→/segment -
特殊文件:
layout.tsx:布局,可嵌套loading.tsx:加载态骨架error.tsx:错误边界(必须"use client")not-found.tsx:404 页面template.tsx:强制每次重新渲染
路由段类型
动态路由/blog/[slog]
对于: /app/blog/[slug]/page.tsx 的文件路由,想要获取 slug 参数的方式如下:
服务端中:
javascript
export default function Page({ params }: { params: { slug: string } }) {
return <div>{params.slug}</div>;
}
客户端中:
typescript
"use client";
import { useParams } from "next/navigation";
const { slug } = useParams<{ slug: string }>();
多级动态路由 /blog/[blogId]/reviews/[reviewId]
对于: /app/blog/[blogId]/reviews/[reviewId]/page.tsx 的文件路由,想要获取 blog 和 reviewId 的方式为:
服务端中:
typescript
export default function ReviewPage({ params }: {
params: { blogId: string; reviewId: string }
}) {
return (
<div>
Review {params.reviewId} for blog {params.blogId}
</div>
);
}
客户端中:
typescript
"use client";
import { useParams } from "next/navigation";
export default function ReviewClient() {
const { blogId, reviewId } = useParams<{ blogId: string; reviewId: string }>();
return (
<div>
Review {reviewId} for blog {blogId}
</div>
);
}
Catch-all路由
这种路由的使用场景是,比如我要访问 /docs/feature1/concept1/example1,这样后面会有很多路由,如何在一个文件中获取?
可以使用:/src/app/docs/[...slug]/page.tsx
这种路由有两种形式:
lua
/app/docs/[...slug]/page.tsx
/app/docs/[[...slug]]/page.tsx
| 文件 | 匹配示例 | 说明 |
|---|---|---|
[...slug] |
/docs/a、/docs/a/b、/docs/a/b/c |
必须 ≥ 1 个段 |
[[...slug]] |
/docs、/docs/a、/docs/a/b |
可选,0 个或多个段 |
比如想要访问 /docs/ 的时候也有页面,那么可以使用:/src/app/docs/[[...slug]]/page.tsx
服务端中获取参数:
csharp
export default function DocPage({ params }: { params: { slug?: string[] } }) {
return (
<div>
Doc Page {params.slug ? params.slug.join("/") : "index"}
</div>
);
}
客户端中获取参数:
csharp
"use client";
import { useParams } from "next/navigation";
export default function DocClient() {
const { slug } = useParams<{ slug?: string[] }>();
return (
<div>
Doc Page {slug ? slug.join("/") : "index"}
</div>
);
}
查询参数获取
这里需要获取的内容是: xxx?q=xxx 这种场景,需要获取 q 是什么
服务端获取:
javascript
export default function Page({ searchParams }: { searchParams: { q?: string } }) {
return <div>{searchParams.q}</div>;
}
客户端获取:
ini
"use client";
import { useSearchParams } from "next/navigation";
const sp = useSearchParams();
const q = sp.get("q");
路由导航
声明式导航,使用Next.js提供的 Link组件:
javascript
import Link from "next/link";
<Link href="/about">About</Link>
服务端重定向:
javascript
import { redirect } from "next/navigation";
redirect("/login");
客户端编程式,使用 useRouter进行跳转:
ini
"use client";
import { useRouter } from "next/navigation";
const router = useRouter();
router.push("/dashboard");
路由控制
404控制:
全局的可以直接写 not-fount.tsx 文件里
代码里触发可以使用:
javascript
import { notFound } from "next/navigation";
if (!data) notFound();
路由组(group)
常用于:代码组织 和 独立布局。
例如我现在有几个页面:
bash
app/
├─ (auth)/
│ ├─ login/page.tsx
│ ├─ register/page.tsx
│ └─ layout.tsx
├─ (marketing)/
│ ├─ about/page.tsx
│ └─ layout.tsx
└─ layout.tsx // 全局布局
他们的URL 会映射为
app/(auth)/login/page.tsx→/loginapp/(marketing)/about/page.tsx→/about
好处如下:
- 代码归类 :把登录/注册页面放在
(auth)文件夹里,看起来整洁,但 URL 没有/auth前缀。 - 局部布局 :在
(auth)/layout.tsx定义一个不带导航栏/页脚的布局,而全局其他页面继续用全局layout.tsx。
私有路由:
- 在目录名前面加下划线
_,该段不会出现在 URL。 - 用于:代码组织/内部实现,不暴露为路由。
如果想要一个路由私有,不被访问,可以使用_开头,例如app/_lib/page.tsx
布局文件:
layout.tsx文件可以定义布局,可以嵌套使用,
javascript
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{children}
</body>
</html>
);
}
Metadata(SEO)
Metadata 是 页面/路由的元信息 ,用于 SEO、社交分享、浏览器行为控制 。Next.js 在 App Router 中内置了 Metadata API ,帮你自动生成 <head> 标签内容,而不用手动写 <head>。可以自动注入 到 HTML 的 <head>,如 <title>、<meta>、<link> 等。
使用:
- 静态定义 :直接在页面或布局文件中
export const metadata。 - 动态生成 :通过
export async function generateMetadata()返回。
📌 作用范围:
- 可以定义在
page.tsx→ 仅作用于该页面。 - 可以定义在
layout.tsx→ 作用于该布局下的所有子页面。 - 支持嵌套,子页面的 metadata 会覆盖/合并父布局的 metadata。
静态Metadata:
在页面/布局文件里直接导出一个 metadata 常量:
typescript
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "My Blog",
description: "Welcome to my blog",
};
适合的场景为:固定的 SEO 信息,如静态 About 页面。这种方式编译时就确定,不依赖运行时数据。
动态Metadata:
如果 metadata 需要依赖 路由参数 / 数据库 / API ,就用 generateMetadata:
typescript
import type { Metadata } from "next";
type Props = {
params: { id: string };
};
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const product = await fetch(`https://api.example.com/products/${params.id}`).then(res => res.json());
return {
title: product.name,
description: product.description,
openGraph: {
images: [product.image],
},
};
}
常用的字段有:
| 字段 | 作用 |
|---|---|
title |
网页标题 |
description |
描述,搜索引擎摘要 |
keywords |
关键词(SEO 较弱化,但仍可加) |
robots |
搜索引擎爬虫规则(如 noindex, nofollow) |
metadata覆盖问题:
- 在
layout.tsx定义的 metadata 会作用于整个子树。 - 在
page.tsx定义的 metadata 会覆盖布局里的同名字段。
例如:
less
app/
├─ layout.tsx (title: "My App")
├─ page.tsx (title: "Home")
└─ blog/
├─ layout.tsx (title: "Blog")
└─ [id]/page.tsx (title: 动态文章标题)
访问:
/→ title = "Home"/blog→ title = "Blog"/blog/123→ title = 动态文章标题
比较好的SEO优化实践为:
- 静态优先,动态按需 :静态页面用
metadata,详情页用generateMetadata。 - 统一 Open Graph:在全局 layout 设置默认 OG 图,文章页再覆盖。
- 社交卡片优化 :为每个文章/产品单独设置
og:image,提升分享效果。
注意:Metadata(不管是
metadata还是generateMetadata)只能在服务端用,不能在客户端用。
其他API
usePathname 当前路径的字符串,使用例子如下:
javascript
"use client";
import { usePathname, useSearchParams } from "next/navigation";
export default function Example() {
const pathname = usePathname(); // "/products/shoes"
const sp = useSearchParams();
const q = sp.get("q"); // "nike"
const page = sp.get("page"); // "2"
return (
<div>
<p>Path: {pathname}</p>
<p>q: {q}</p>
<p>page: {page}</p>
</div>
);
}