Skip to content
 

Next.js 的客户端页面路由详解

更新: 11/26/2025字数: 0 字 时长: 0 分钟

1. 路由的基本概念

Next.js 15(App Router 模式) 中,路由主要依赖 文件系统路由(File-system routing),所有页面都放在 app/ 目录下:

app/
 ├── page.tsx          // 对应 "/"
 ├── about/
 │    └── page.tsx     // 对应 "/about"
 └── blog/
      └── [id]/
           └── page.tsx  // 动态路由 "/blog/123"
  • 服务端渲染 (SSR):页面初次加载时由服务器返回 HTML。
  • 客户端路由 (CSR):在页面之间跳转时,Next.js 使用 客户端路由器(基于 React Router-like 机制)来实现 无刷新切换

2. 客户端路由的核心

客户端路由由 next/navigation 提供,主要用到两个 Hook / API

(1) useRouter

用于编程式导航,和 React Router 的 useNavigate 类似。

tsx
"use client";

import { useRouter } from "next/navigation";

export default function Home() {
  const router = useRouter();

  return <button onClick={() => router.push("/about")}>去 About 页面</button>;
}
  • router.push("/path"):跳转到某个页面,保留历史记录。
  • router.replace("/path"):跳转但不保留历史记录。
  • router.back():回退。

推荐使用 <Link> 来实现 预加载和无刷新跳转

tsx
import Link from "next/link";

export default function NavBar() {
  return (
    <nav>
      <Link href="/">首页</Link>
      <Link href="/about">关于我们</Link>
    </nav>
  );
}
  • <Link> 默认会预加载目标页面的代码,提高切换速度。
  • 只有当目标在视口中可见时才会自动预加载。

(3) redirect()

用于服务端或客户端强制重定向。

tsx
import { redirect } from "next/navigation";

export default function Page() {
  const isLogin = false;
  if (!isLogin) {
    redirect("/login");
  }
  return <div>已登录</div>;
}

3. 动态路由与客户端获取参数

动态路由文件夹 [id] 会捕获 URL 中的参数。

文件夹嵌套结构:app/blog/[id]/page.tsx 如下:

app/
 ├── blog/
 │    └── [id]/
 │         └── page.tsx  // 动态路由 "/blog/123"

组件获取如下:

tsx
// app/blog/[id]/page.tsx
"use client";
import { useParams } from "next/navigation";
export default function BlogPage() {
  const params = useParams(); // { id: "123" }
  return <h1>文章 ID: {params.id}</h1>;
}

// 如果是 服务端渲染,直接通过 `params` 获取:
export default function BlogPage({ params }: { params: { id: string } }) {
  return <h1>文章 ID: {params.id}</h1>;
}

上面这种方式只能获取一层动态路由参数,不能获取嵌套路由参数(例如 /blog/123/comment/456就报错了)。

路由结构改用[...id]如下:

app/
 ├── blog/
 │    └── [...id]/
 │         └── page.tsx  // 动态路由 "/blog/123/comment/456"

这样就可以获取到多层动态路由参数了。

还有一种情况我们想让这个路由参数是可选的,有没有都可以,那么在外层再包裹一层中括号变成了[[...id]],如下:

app/
 ├── blog/
 │    └── [[...id]]/
 │         └── page.tsx  // 动态路由 "/blog/123/comment/456"

动态路由总结:

  • [id]:只能获取一层路由参数,例如 /blog/123
  • [...id]:可以获取多层路由参数,例如 /blog/123/comment/456
  • [[...id]]:路由参数是可选的,有没有都可以,例如 /blog/123/blog/123/comment/456

4. 客户端路由和服务端路由的区别

特性服务端渲染 (SSR)客户端路由 (CSR)
初次加载服务器生成 HTML前端只加载入口 HTML
跳转整页刷新 / SSR无刷新跳转,速度快
数据获取通过 fetch() / server actionsuseEffect / SWR / TanStack Query
SEO较弱(需 SSR/ISR 支持)

Next.js 的优势就是 两者结合 —— 初次请求走 SSR,后续页面切换走 CSR。

5. 路由案例

整个案例包含:

  • 首页 /:有一个跳转按钮,去详情页
  • 详情页 /blog/[id]:显示文章 ID
  • 导航栏组件:全局无刷新跳转

项目结构

app/
 ├── layout.tsx
 ├── page.tsx          // 首页
 ├── blog/
 │    └── [id]/
 │         └── page.tsx  // 动态详情页
 └── components/
      └── NavBar.tsx   // 导航栏

1. app/layout.tsx (全局布局)

tsx
import "./globals.css";
import NavBar from "./components/NavBar";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <NavBar />
        <main>{children}</main>
      </body>
    </html>
  );
}

2. app/components/NavBar.tsx (导航栏)

tsx
"use client";

import Link from "next/link";

export default function NavBar() {
  return (
    <nav style={{ display: "flex", gap: "20px", marginBottom: "20px" }}>
      <Link href="/">首页</Link>
      <Link href="/blog/123">文章 123</Link>
      <Link href="/blog/456">文章 456</Link>
    </nav>
  );
}

3. app/page.tsx (首页)

tsx
"use client";

import { useRouter } from "next/navigation";

export default function HomePage() {
  const router = useRouter();

  return (
    <div>
      <h1>首页</h1>
      <button onClick={() => router.push("/blog/999")} style={{ marginTop: "20px" }}>
        去文章 999
      </button>
    </div>
  );
}

4. app/blog/[id]/page.tsx (详情页)

tsx
"use client";

import { useParams, useRouter } from "next/navigation";

export default function BlogDetail() {
  const params = useParams(); // { id: "123" }
  const router = useRouter();

  return (
    <div>
      <h1>文章详情</h1>
      <p>当前文章 ID: {params.id}</p>
      <button onClick={() => router.back()}>返回上一页</button>
    </div>
  );
}

效果

  1. 打开 / → 首页

    • 可以点击「去文章 999」按钮 → 跳转 /blog/999
  2. 在导航栏点击 /blog/123/blog/456 → 跳转不同文章详情

  3. 在详情页点「返回上一页」 → 回到首页

我见青山多妩媚,料青山见我应如是。