后端转全栈之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>
  );
}
相关推荐
OEC小胖胖6 小时前
Next.js数据获取入门:`getStaticProps` 与 `getServerSideProps`
前端·前端框架·web·next.js
薛定谔的算法6 小时前
JavaScript栈的实现与应用:从基础到实战
前端·javascript·算法
深圳外环高速6 小时前
React 受控组件如何模拟用户输入
前端·react.js
土了个豆子的6 小时前
03.缓存池
开发语言·前端·缓存·visualstudio·c#
手握风云-7 小时前
JavaEE 进阶第四期:开启前端入门之旅(四)
前端
魔云连洲7 小时前
React中的合成事件
前端·javascript·react.js
六月的可乐7 小时前
【干货推荐】AI助理前端UI组件-悬浮球组件
前端·人工智能·ui
呼啦啦呼_7 小时前
Echarts自定义地图显示区域,显示街道学校等区域,对原有区域拆分
前端