前言
大家好,我是鲫小鱼。是一名不写前端代码的前端工程师,热衷于分享非前端的知识,带领切图仔逃离切图圈子,欢迎关注我,微信公众号:《鲫小鱼不正经》。欢迎点赞、收藏、关注,一键三连!!
第三章:Next.js Pages Router 深度解析
教程简介
在本章中,我们将深入探索 Next.js 传统的路由方式——Pages Router。尽管 Next.js 13 引入了更强大的 App Router,但 Pages Router 仍在许多现有项目中广泛使用,并且是理解 Next.js 路由核心机制的基石。我们将详细解析 Pages Router 的文件系统路由机制,学习如何创建各种复杂的路由模式,包括动态路由、嵌套路由和 Catch-all 路由,并掌握 next/link 和 useRouter 等核心 API 的高级用法。通过本章的学习,您将能够灵活地构建基于 Pages Router 的多页面应用程序,并为理解未来的 App Router 打下坚实基础。
理论讲解
1.1 Pages Router 的文件系统路由机制:约定优于配置
Next.js Pages Router 的核心思想是文件系统路由。这意味着您无需手动配置路由表,只需在 pages/ 目录下创建文件,Next.js 就会根据文件的路径和名称自动生成对应的路由。这种"约定优于配置"的原则大大简化了路由管理。
- 基本路由:
pages/about.js会自动映射到/about路径。 - 根路由:
pages/index.js映射到根路径/。 - 嵌套路由:
pages/blog/first-post.js映射到/blog/first-post路径。您可以在pages目录下创建子目录来实现嵌套路由。
1.2 如何处理复杂的路由模式
1.2.1 动态路由 (Dynamic Routes)
当您需要根据不同的数据 ID 或 Slug 生成多个页面时,可以使用动态路由。在文件名或目录名中使用方括号 [] 来表示动态部分。
- 文件级动态路由:
pages/posts/[id].js会匹配/posts/1、/posts/abc等路径,id可以在页面组件中通过useRouter获取。 - 目录级动态路由:
pages/users/[id]/profile.js会匹配/users/123/profile,id同样可获取。
1.2.2 嵌套动态路由
您可以在动态路由中继续嵌套动态路由,以处理更复杂的 URL 结构。
pages/products/[category]/[slug].js:可以匹配/products/electronics/iphone-14,其中category和slug都是动态参数。
1.2.3 Catch-all 路由 (Catch-all Routes)
Catch-all 路由用于捕获所有子路径。在文件名中使用三个点 [...slug].js。
pages/docs/[...slug].js:会匹配/docs/a、/docs/a/b、/docs/a/b/c等路径。所有捕获的路径段将作为数组在slug参数中获取。- 可选 Catch-all 路由:通过双层方括号
[[...slug]].js,可以使其匹配包含路径参数和不包含路径参数的情况。例如,pages/blog/[[...slug]].js既能匹配/blog也能匹配/blog/a/b。
1.3 路由优先级
当存在多个路由可能匹配同一个 URL 时,Next.js 会遵循一定的优先级规则:
- 静态路由:精确匹配优先,如
pages/about.js优先于动态路由。 - 动态路由:如
pages/posts/[id].js。 - Catch-all 路由:如
pages/docs/[...slug].js。 - 可选 Catch-all 路由:如
pages/blog/[[...slug]].js。
例如,pages/posts/new.js 会优先匹配 /posts/new,而不会被 pages/posts/[id].js捕获。
1.4 客户端导航与 next/link 组件
在 Pages Router 中,我们使用 next/link 组件进行客户端路由跳转。它能够预加载页面,实现平滑的页面过渡,而无需浏览器刷新。
-
基本用法:
import Link from 'next/link'; function HomePage() { return ( <Link href="/about"> <a>关于我们</a> </Link> ); } -
带参数的动态路由:
import Link from 'next/link'; function PostList() { const postId = 123; return ( <Link href={`/posts/${postId}`}> <a>查看文章 {postId}</a> </Link> ); } -
passHref: 当子组件是自定义的 React 组件(而不是原生 DOM 元素<a>)时,需要添加passHref属性,确保href属性能被正确传递。 -
prefetch: 默认情况下,next/link会在视口中自动预加载链接的页面。您也可以通过prefetch={false}禁用预加载。
1.5 编程式导航与 useRouter 钩子
除了 next/link,我们还可以使用 next/router 中的 useRouter 钩子来实现编程式导航,例如在表单提交后跳转、条件跳转等。
import { useRouter } from 'next/router';
function MyComponent() {
const router = useRouter();
const handleClick = () => {
// 跳转到指定路径
router.push('/dashboard');
// 带查询参数跳转
router.push({
pathname: '/search',
query: { keyword: 'nextjs' },
});
// 替换当前历史记录
router.replace('/login');
// 返回上一页
router.back();
};
// 获取路由参数
const { id } = router.query; // 如果是 /posts/[id] 页面
return <button onClick={handleClick}>跳转</button>;
}
代码示例
2.1 创建一个多层级博客系统 (/posts/[category]/[slug].js)
我们将创建一个简单的多层级博客系统,演示如何利用动态路由和嵌套动态路由来组织文章。
-
创建目录和文件结构:
pages/ └── posts/ └── [category]/ └── [slug].tsx -
pages/posts/[category]/[slug].tsx内容:// pages/posts/[category]/[slug].tsx import { useRouter } from 'next/router'; import Head from 'next/head'; import Link from 'next/link'; const postsData: { [key: string]: { [key: string]: { title: string; content: string } } } = { frontend: { 'nextjs-intro': { title: 'Next.js 入门', content: '这是关于 Next.js 入门的第一篇文章。', }, 'react-hooks': { title: 'React Hooks 详解', content: '深入理解 React Hooks 的使用和原理。', }, }, backend: { 'nodejs-api': { title: 'Node.js API 开发', content: '使用 Node.js 构建 RESTful API。', }, }, }; export default function BlogPost() { const router = useRouter(); const { category, slug } = router.query; if (!category || !slug) { return <p>加载文章中...</p>; } const post = postsData[category as string]?.[slug as string]; if (!post) { return <h1>404 - 文章未找到</h1>; } return ( <> <Head> <title>{post.title}</title> </Head> <div> <Link href="/"> <a>返回首页</a> </Link> <h1>{post.title}</h1> <p>分类: {category}</p> <div>{post.content}</div> </div> </> ); } -
创建文章列表页 (
pages/posts/index.tsx) :// pages/posts/index.tsx import Head from 'next/head'; import Link from 'next/link'; const categories = { frontend: [ { slug: 'nextjs-intro', title: 'Next.js 入门' }, { slug: 'react-hooks', title: 'React Hooks 详解' }, ], backend: [ { slug: 'nodejs-api', title: 'Node.js API 开发' }, ], }; export default function PostsList() { return ( <> <Head> <title>所有文章</title> </Head> <div> <h1>所有文章</h1> {Object.entries(categories).map(([categoryName, posts]) => ( <div key={categoryName}> <h2>{categoryName}</h2> <ul> {posts.map((post) => ( <li key={post.slug}> <Link href={`/posts/${categoryName}/${post.slug}`}> <a>{post.title}</a> </Link> </li> ))} </ul> </div> ))} </div> </> ); }
现在,运行 pnpm dev,访问 /posts/frontend/nextjs-intro 或 /posts/backend/nodejs-api,您将看到对应的文章内容。同时,访问 /posts 可以看到文章列表,并通过 next/link 跳转。
2.2 演示 next/link 和 useRouter 的高级用法
我们将创建一个包含查询参数的页面,并使用 useRouter 进行编程式导航和获取参数。
-
创建
pages/search.tsx文件:// pages/search.tsx import { useRouter } from 'next/router'; import Head from 'next/head'; export default function SearchPage() { const router = useRouter(); const { keyword, category } = router.query; return ( <> <Head> <title>搜索结果</title> </Head> <div> <h1>搜索结果</h1> <p>搜索关键词: {keyword || '无'}</p> <p>搜索分类: {category || '无'}</p> <button onClick={() => router.push('/posts')}> 返回文章列表 </button> <button onClick={() => router.back()}>返回上一页</button> </div> </> ); } -
在
pages/index.tsx(首页) 中添加搜索链接和编程式导航示例:// pages/index.tsx (部分修改) import Head from 'next/head'; import Link from 'next/link'; import { useRouter } from 'next/router'; // 导入 useRouter export default function Home() { const router = useRouter(); const handleSearch = () => { router.push({ pathname: '/search', query: { keyword: 'Next.js', category: '前端' }, }); }; return ( <> <Head> <title>我的 Next.js 教程</title> <meta name="description" content="Next.js 教程第一章" /> <link rel="icon" href="/favicon.ico" /> </Head> <main> <h1>欢迎来到我的 Next.js 教程!</h1> <p>这是一个使用 Next.js 构建的简单首页。</p> {/* 使用 Link 跳转到文章列表 */} <p> <Link href="/posts"> <a>查看所有文章</a> </Link> </p> {/* 使用编程式导航进行搜索 */} <p> <button onClick={handleSearch}> 搜索 Next.js 文章 </button> </p> </main> </> ); }
现在,运行 pnpm dev,点击首页的"搜索 Next.js 文章"按钮,会跳转到 /search?keyword=Next.js&category=前端 页面,并显示对应的查询参数。
实战项目
3.1 实现一个内容管理系统 (CMS) 的前端导航
目标:构建一个模拟 CMS 的前端导航,包含文章列表、文章详情和分类页,利用 Pages Router 实现。
-
项目结构 (如果您按照代码示例创建了,则已具备大部分):
pages/ ├── index.tsx // 首页 ├── posts/ │ ├── index.tsx // 文章列表页 │ └── [category]/ │ └── [slug].tsx // 文章详情页 (动态路由) ├── categories/ │ └── [name].tsx // 分类详情页 (动态路由) └── about.tsx // 关于页面 -
pages/categories/[name].tsx(分类详情页):// pages/categories/[name].tsx import { useRouter } from 'next/router'; import Head from 'next/head'; import Link from 'next/link'; const allPosts = { frontend: [ { slug: 'nextjs-intro', title: 'Next.js 入门' }, { slug: 'react-hooks', title: 'React Hooks 详解' }, ], backend: [ { slug: 'nodejs-api', title: 'Node.js API 开发' }, ], }; export default function CategoryPage() { const router = useRouter(); const { name } = router.query; if (!name) { return <p>加载分类中...</p>; } const postsInCategory = allPosts[name as string]; if (!postsInCategory) { return <h1>404 - 分类未找到</h1>; } return ( <> <Head> <title>{name} 分类</title> </Head> <div> <h1>分类: {name}</h1> <ul> {postsInCategory.map((post) => ( <li key={post.slug}> <Link href={`/posts/${name}/${post.slug}`}> <a>{post.title}</a> </Link> </li> ))} </ul> <Link href="/posts"> <a>返回所有文章</a> </Link> </div> </> ); } -
更新
pages/posts/index.tsx,添加分类链接:// pages/posts/index.tsx (更新部分) import Head from 'next/head'; import Link from 'next/link'; const categoriesData = { frontend: [ { slug: 'nextjs-intro', title: 'Next.js 入门' }, { slug: 'react-hooks', title: 'React Hooks 详解' }, ], backend: [ { slug: 'nodejs-api', title: 'Node.js API 开发' }, ], }; export default function PostsList() { return ( <> <Head> <title>所有文章</title> </Head> <div> <h1>所有文章</h1> {Object.entries(categoriesData).map(([categoryName, posts]) => ( <div key={categoryName}> <h2> <Link href={`/categories/${categoryName}`}> <a>{categoryName}</a> </Link> </h2> <ul> {posts.map((post) => ( <li key={post.slug}> <Link href={`/posts/${categoryName}/${post.slug}`}> <a>{post.title}</a> </Link> </li> ))} </ul> </div> ))} </div> </> ); }
现在,您可以通过以下方式测试您的 CMS 导航:
- 访问
/posts查看所有文章和分类链接。 - 点击分类名称(如 "frontend")跳转到分类详情页
/categories/frontend。 - 点击文章标题跳转到文章详情页
/posts/frontend/nextjs-intro。
通过本章的学习,您已经对 Next.js Pages Router 的文件系统路由机制有了全面的理解,并掌握了动态路由、嵌套路由、Catch-all 路由的创建和使用。同时,您也熟练运用了 next/link 和 useRouter 进行页面导航。下一章我们将探讨 Next.js 13+ 引入的 App Router 核心概念,它为 Next.js 的全栈能力带来了革命性的变化。
最后感谢阅读!欢迎关注我,微信公众号:
《鲫小鱼不正经》。欢迎点赞、收藏、关注,一键三连!!!