后端转全栈之Next.js 路由系统App Router

Next.js 路由系统App Router

本文概括:

  1. 文件即路由规则 :文件夹结构决定 URL,对应的特殊文件有 layout.tsxloading.tsxerror.tsxnot-found.tsxtemplate.tsx

  2. 路由段类型 :支持动态路由、多级动态路由、Catch-all([...slug][[...slug]])等。

  3. 参数获取方式

    • 动态段参数:服务端用 params,客户端用 useParams
    • 查询参数:服务端用 searchParams,客户端用 useSearchParams
  4. 路由导航

    • 声明式:<Link />
    • 服务端:redirect()
    • 客户端:useRouter.push()
  5. 路由控制

    • 404 页面:not-found.tsxnotFound()
    • 路由组 (group) 和私有 _ 路由。
  6. 布局文件layout.tsx 可嵌套,用于 UI 框架复用。

  7. Metadata(SEO)

    • 静态:export const metadata
    • 动态:generateMetadata()
    • 继承 & 覆盖规则,SEO 最佳实践。
  8. 其他 APIusePathnameuseParams 对比,适用场景。

服务端&客户端 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 的文件路由,想要获取 blogreviewId 的方式为:

服务端中:

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>
  );
}
相关推荐
崔庆才丨静觅13 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606113 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了14 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅14 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅14 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅14 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment14 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅15 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊15 小时前
jwt介绍
前端
爱敲代码的小鱼15 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax