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
→/login
app/(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>
);
}