主题
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():回退。
(2) <Link /> 组件
推荐使用 <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 actions | useEffect / 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>
);
}效果
打开
/→ 首页- 可以点击「去文章 999」按钮 → 跳转
/blog/999
- 可以点击「去文章 999」按钮 → 跳转
在导航栏点击
/blog/123或/blog/456→ 跳转不同文章详情在详情页点「返回上一页」 → 回到首页