在前端框架(如 React、Vue 等)或移动端开发中,路由系统是实现页面 / 界面导航的核心机制。Next.js 采用 文件系统路由(File System Routing),即根据项目目录结构自动生成路由。
Next.js 目前有两套路由解决方案,Next.js 从 v13.0.0 版本开始引入 App Router,之前的方案称之为“Pages Router”,目前的方案称之为“App Router”,两套路由系统可以相互兼容,Next.js v14+ 默认启用,本文基于 next v15版本说明。
关键区别
特性 | Pages Router | App Router |
---|---|---|
目录结构 | 基于 pages/ 目录 | 基于 app/ 目录 |
组件类型 | 仅支持客户端组件 | 支持 Server Components(默认) |
数据获取 | getServerSideProps/getStaticProps | 直接在组件中使用 async/await |
布局方式 | 手动实现 Layout 组件 | 自动支持嵌套布局(layout.js) |
文件类型 | .js/.jsx/.tsx | 新增 .server.jsx/.client.jsx |
四种导航方式
Next.js 提供了多种路由导航方式,每种方式都有其特定的使用场景和优势。
1. 组件
是 Next.js 提供的一个内置组件,用于在页面之间进行客户端导航。它扩展了 HTML 的 a 标签,提供了预获取和路由之间的客户端导航功能。这是在 Next.js 中推荐的路由导航方式。基础使用
import Link from 'next/link';
export default function Page() {
return <Link href="/dashboard">Dashboard</Link>;
}
动态路由
import Link from 'next/link';
export default function Page() {
const items = [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
];
return (
<ul>
{items.map(item => (
<li key={item.id}>
<Link href={`/items/${item.id}`}>{item.name}</Link>
</li>
))}
</ul>
);
}
获取当前路径名
import { usePathname } from 'next/navigation';
export default function Page() {
const pathname = usePathname();
return <div>Current Path: {pathname}</div>;
}
2. useRouter (客户端组件)
允许你以编程方式改变来自客户端组件的路由,这种方式适用于在事件处理函数或异步操作中进行导航。
'use client';
import { useRouter } from 'next/navigation';
export default function Page() {
const router = useRouter();
return (
<button type="button" onClick={() => router.push('/dashboard')}>
Dashboard
</button>
);
}
useRouter 的常用方法:
- push:在客户端将新地址添加到浏览器历史栈中。
- replace:在客户端导航时,将现在的访问地址替换成目标地址。
- refresh:刷新当前路由。
- prefetch:预获取提供的路由,加快客户端导航速度。
- back:向后导航到浏览器历史栈中的上一页。
- forward:向前导航到浏览器历史栈中的下一页。
3. redirect 功能(服务端组件)
redirect 函数用于在服务器组件中进行路由跳转。这种方式通常用于在服务器端根据某些条件重定向用户。
import { redirect } from 'next/navigation'
async function fetchTeam(id: string) {
const res = await fetch('https://...')
if (!res.ok) return undefined
return res.json()
}
export default async function Profile({
params,
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params
if (!id) {
redirect('/login')
}
const team = await fetchTeam(id)
if (!team) {
redirect('/join')
}
// ...
}
- redirect 默认返回 307(临时重定向)状态代码。当在服务器操作中使用时,它会返回 303(请参阅其他),通常用于由于 POST 请求而重定向到成功页面。
- 如果你想在渲染过程之前重定向,可以使用 next.config.js 或 中间件。
中间件
中间件允许你在请求完成之前运行代码。然后,根据传入的请求,你可以通过重写、重定向、修改请求或响应标头或直接响应来修改响应。
中间件在缓存内容和路由匹配之前运行。
常见应用场景
- 读取部分传入请求后快速重定向
- 根据A/B测试或实验重写到不同的页面
- 修改所有页面或部分页面的标题
- 拦截请求,实现鉴权、日志
中间件中的 NextResponse
- redirect 传入请求到不同的 URL
- rewrite 通过显示给定 URL 进行响应
- 设置 API 路由,getServerSideProps 和 rewrite 目标的请求标头。
- 设置响应 cookie
- 设置响应标头
中间件的重定向应用
1. 鉴权
中间件允许你在请求完成之前运行代码。然后,根据传入请求,使用 NextResponse.redirect 重定向到不同的 URL。如果你想根据条件(例如身份验证、会话管理等)重定向用户或有大量重定向(非登录页验证 token)。
例如,如果用户未通过身份验证,则将用户重定向到 /login 页面:
```
import { NextResponse, NextRequest } from 'next/server'
import { authenticate } from 'auth-provider'
export function middleware(request: NextRequest) {
const isAuthenticated = authenticate(request)
// If the user is authenticated, continue as normal
if (isAuthenticated) {
return NextResponse.next()
}
// Redirect to login page if not authenticated
return NextResponse.redirect(new URL('/login', request.url))
}
export const config = {
matcher: '/dashboard/:path*',
}
```
- 中间件在 redirects 之后、next.config.js 中、渲染之前运行。
- 拦截请求
export function middleware(request) {
if (!request.cookies.has('token')) {
return Response.redirect(new URL('/login', request.url));
}
}
4. 原生 history API
除了 Next.js 提供的路由功能外,你还可以使用浏览器的原生 History API 进行路由跳转。这种方式适用于需要更细粒度控制路由历史的情况,不会重新加载页面。
export default function Page() {
return (
<button type="button" onClick={() => window.history.pushState({}, '', '/dashboard')}>
Dashboard
</button>
);
}
动态路由
Next.js 提供了强大的动态路由功能,允许开发者根据动态参数生成页面。
1. 概念
动态路由允许你根据动态参数生成页面。例如,一个博客网站可能需要为每篇文章生成一个单独的页面,而文章的 ID 是动态的。通过动态路由,你可以使用一个页面模板来处理所有这些动态页面。
2. 创建动态路由
在 Next.js 中,动态路由通过在页面文件名中使用方括号 [] 来定义。例如,如果你有一个页面文件名为 [id].js,那么这个页面将能够处理所有类似 /123、/456 的路径,其中 123 和 456 是动态参数。
pages/
├── posts/
│ └── [id].js
└── users/
└── [username].js
获取动态参数
在动态路由页面中,你可以通过 useRouter 钩子或 getServerSideProps、getStaticProps 等方法来获取动态参数。
特性 | useRouter 钩子 | getServerSideProps | getStaticProps |
---|---|---|---|
使用环境 | 仅客户端 | 仅服务端 | 仅构建时 |
获取参数方式 | const { id } = useRouter().query | context.params | context.params |
数据获取时机 | 客户端渲染时 | 每次请求时 | 构建时 |
SEO 友好性 | 较差(客户端渲染) | 优秀(服务端渲染) | 优秀(静态生成) |
性能影响 | 客户端解析,影响较小 | 每次请求都执行,影响较大 | 仅构建时执行,运行时无负担 |
适用场景 | 不需要SEO的交互部分 | 需要实时数据的页面 | 内容不频繁变化的页面 |
类型支持 | 需要手动类型断言 | 自动推断类型 | 自动推断类型 |
静态生成支持 | 不适用 | 不适用 | 支持,需配合 getStaticPaths |
访问请求对象 | 不可访问 | 可通过 context.req 访问 | 不可访问 |
访问响应对象 | 不可访问 | 可通过 context.res 访问 | 不可访问 |
重定向处理 | 使用 router.push | 可在函数内返回 redirect 对象 | 可在函数内返回 redirect 对象 |
重新验证支持 | 不适用 | 不适用 | 支持 revalidate 参数 |
预渲染行为 | 客户端渲染 | 服务端渲染 | 静态生成 |
动态路由必需配置 | 不需要 | 不需要 | 需要 getStaticPaths |
策略流程图
并行路由
(未完待续)