前言
在前端 Web 开发中,路由和导航系统是构建用户友好体验的关键部分。Next.js 15 为开发者提供了一套完整而强大的路由解决方案,不仅支持 声明式和命令式 的导航方式,还内置了 预加载、流式渲染 等性能优化机制,让我们能够轻松打造出流畅自然的页面跳转体验。通过合理运用这套路由系统,我们可以有效管理应用状态,提升用户体验。
本文介绍 Next.js 中的四大导航机制:
<Link>
组件:Next.js 推荐的导航方式,支持预加载和客户端导航useRouter
Hook:用于客户端组件中的编程式导航redirect
函数:专门用于服务端组件的重定向History API
:用于高度定制化的 URL 操作
<Link>
组件
Next.js 的 <Link>
组件是 <a>
标签的增强版,它不仅继承了 <a>
标签的所有功能,还在此基础上增加了预获取(Prefetching)和客户端路由导航等高级特性。它是 Next.js 中实现页面导航最常用且推荐的方式,能够显著提升用户体验。
基础使用
使用 <Link>
组件进行导航非常简单,你只需要将 href
属性指向目标路由即可。例如,要导航到一个名为 /dashboard
的页面:
javascript
import Link from 'next/link'
export default function Page() {
return <Link href="/dashboard">Dashboard</Link>
}
动态路由与 <Link>
<Link>
组件也完美支持动态路由。你可以通过模板字符串或对象形式来构建动态链接:
javascript
import Link from 'next/link'
export default function PostList({ posts }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>
<Link href={`/blog/${post.slug}`}>{post.title}</Link>
</li>
))}
</ul>
)
}
Next.js 导航优化机制
<Link>
组件之所以能够提供如此流畅的导航体验,得益于 Next.js 内置的几项优化机制:
1. 预获取(Prefetching)
预获取是 Next.js 的一项核心优化功能。当用户点击链接时,Next.js 会在后台自动预加载链接指向页面的数据和代码。这意味着当用户真正点击链接时,页面几乎可以瞬间加载完成,大大减少了等待时间,提升了用户感知的性能。
-
默认行为 :在生产环境中,
<Link>
组件默认会预获取其指向的路由。当链接进入视口时,Next.js 会在后台静默地下载并解析目标页面的 JavaScript、CSS 和数据。 -
禁用预获取 :如果你不希望某个链接进行预获取,可以设置
prefetch={false}
:javascriptimport Link from 'next/link' export default function Page() { return <Link href="/dashboard" prefetch={false}>Dashboard</Link> }
禁用预获取通常用于那些不经常访问或数据量非常大的页面,以避免不必要的网络请求。
2. 流式渲染(Streaming)
流式渲染是 Next.js 13 引入的一项重要特性,它允许你将页面的 UI 分割成更小的、独立的单元,并逐步将它们从服务器发送到客户端。这意味着用户无需等待整个页面加载完成,就可以看到部分内容并开始交互,从而显著提升了初始加载性能和用户体验。
<Link>
组件与流式渲染结合使用时,可以实现更平滑的页面过渡。当用户导航到一个新页面时,Next.js 可以先渲染页面的骨架(例如 layout.js
),然后逐步填充动态内容(例如通过 loading.js
或 Suspense
)。
-
结合
loading.js
:当导航到新路由时,Next.js 会立即显示目标路由的loading.js
文件中的 UI,而不是等待所有数据加载完成。这为用户提供了即时反馈,减少了白屏时间。jsx// app/dashboard/loading.js export default function Loading() { return <h2>Loading dashboard...</h2> }
-
结合 React
Suspense
:你可以在组件内部使用<Suspense>
来包裹异步加载的内容。当异步内容加载时,Suspense
会显示一个 fallback UI,直到内容准备就绪。jsx// app/dashboard/page.js import { Suspense } from 'react' import { PostFeed, Spinner } from './components' export default function Page() { return ( <section> <Suspense fallback={<Spinner />}> <PostFeed /> </Suspense> </section> ) }
这种方式使得页面在加载过程中保持响应,用户体验更加流畅。
3. 客户端过渡(Client-side transitions)
客户端过渡是指在客户端(浏览器)完成页面之间的导航,而不是每次都向服务器发送完整的页面请求。当用户点击 <Link>
组件时,Next.js 会拦截默认的浏览器导航行为,并通过 JavaScript 在客户端更新 URL 和渲染新的页面内容。这避免了浏览器重新加载整个页面,从而实现了快速、无刷新的页面切换。
-
原理 :Next.js 利用 History API(
pushState
和replaceState
)来改变 URL,并结合 React 的组件渲染机制,只更新发生变化的组件部分,而不是重新渲染整个 DOM 树。 -
优势:
- 速度快:无需重新加载整个页面,只更新必要部分,响应速度更快。
- 用户体验好:页面切换平滑,没有闪烁或白屏。
- 状态保持:在某些情况下,可以更好地保持页面状态(例如滚动位置、表单输入)。
跳转行为设置
App Router 的默认行为是滚动到新路由的顶部,或者在前进后退导航时维持之前的滚动距离。如果你想要禁用这个行为,可以给 <Link>
组件传递一个 scroll={false}
属性:
javascript
// next/link
<Link href="/dashboard" scroll={false}>
Dashboard
</Link>
或者在使用 router.push
和 router.replace
的时候,设置 scroll: false
:
javascript
// useRouter
import { useRouter } from 'next/navigation'
const router = useRouter()
router.push('/dashboard', { scroll: false })
强制刷新行为
在某些情况下,你可能需要强制刷新页面以获取最新数据或重置组件状态。在 Next.js 13 及更高版本中,推荐使用 router.refresh()
方法来实现此目的,而不是依赖 <Link>
组件的 refresh
属性(该属性已被移除或不推荐使用)。
router.refresh()
会刷新当前路由,但会保留客户端状态(例如输入框内容、滚动位置),并重新请求服务器组件的数据。这对于需要重新验证数据的场景非常有用,例如用户提交表单后更新列表。
javascript
'use client'
import { useRouter } from 'next/navigation'
export default function MyComponent() {
const router = useRouter()
const handleRefresh = () => {
router.refresh() // 刷新当前路由
}
return (
<button onClick={handleRefresh}>刷新数据</button>
)
}
useRouter()
Hook
useRouter
是 Next.js 提供的一个客户端 Hook,它允许你在客户端组件中以编程方式进行路由导航和访问路由信息。当你需要更复杂的导航逻辑,例如基于用户操作、表单提交或异步数据加载后进行跳转时,useRouter
是一个非常强大的工具。
基础使用
要使用 useRouter
,首先需要在客户端组件中导入它,然后调用 useRouter()
来获取 router
实例:
javascript
'use client'
import { useRouter } from 'next/navigation'
export default function Page() {
const router = useRouter()
return (
<button type="button" onClick={() => router.push('/dashboard')}>
Go to Dashboard
</button>
)
}
注意,useRouter
只能在客户端组件中使用,因此你的组件文件顶部需要有 'use client'
指令。
常用方法
router
实例提供了多种方法来控制路由:
-
router.push(href, options)
: 将新路由添加到历史堆栈,实现页面跳转。options
可以包含scroll: false
来禁用滚动到顶部。javascriptrouter.push('/posts/123', { scroll: false });
-
router.replace(href, options)
: 替换历史堆栈中的当前路由,用户无法通过浏览器后退按钮返回上一个页面。options
同样可以包含scroll: false
。javascriptrouter.replace('/login', { scroll: false });
-
router.back()
: 返回历史堆栈中的上一个页面,类似于浏览器后退按钮。 -
router.forward()
: 前进到历史堆栈中的下一个页面,类似于浏览器前进按钮。 -
router.refresh()
: 刷新当前路由,重新请求服务器组件的数据,但保留客户端状态。这对于重新验证数据的场景非常有用。 -
router.prefetch(href)
: 手动预获取指定路由的数据和代码。当你希望在用户点击链接之前,提前加载某个页面资源时非常有用。
获取路由信息
useRouter
还提供了访问当前路由信息的能力:
router.pathname
: 获取当前路由的路径名(例如/blog/my-post
)。router.query
: 获取当前路由的查询参数(例如{ id: '123' }
)。router.asPath
: 获取浏览器中显示的完整路径,包括查询参数(例如/blog/my-post?id=123
)。
示例:动态路由带参数
javascript
'use client'
import { useRouter } from 'next/navigation'
export default function PostDetail({ postId }) {
const router = useRouter()
const navigateToComments = () => {
router.push(`/posts/${postId}/comments`)
}
return (
<div>
<h1>Post {postId}</h1>
<button onClick={navigateToComments}>View Comments</button>
</div>
)
}
redirect
函数
在 Next.js 的 App Router 中,redirect
函数是一个强大的服务器端 API,用于在服务器端组件或 Route Handlers 中进行重定向。与客户端的 useRouter
不同,redirect
在服务器上执行,这意味着它可以在页面渲染之前就将用户导向另一个 URL,这对于认证、权限检查或处理旧 URL 等场景非常有用。
基础使用
redirect
函数非常直观,你只需要传入目标 URL 即可:
javascript
import { redirect } from 'next/navigation'
async function fetchTeam(id) {
const res = await fetch('https://...')
if (!res.ok) return undefined
return res.json()
}
export default async function Profile({ params }) {
const team = await fetchTeam(params.id)
if (!team) {
redirect('/login') // 如果团队不存在,重定向到登录页
}
// ... 渲染 Profile 页面内容
}
在上面的例子中,如果 fetchTeam
函数返回 undefined
(表示团队不存在),用户将被立即重定向到 /login
页面,而不会渲染 Profile
页面的任何内容。
适用场景
- 认证和授权:检查用户是否登录或是否有权限访问某个页面。如果未通过,则重定向到登录页或权限不足页。
- 数据不存在:当请求的数据不存在时,重定向到 404 页面或错误页面。
- URL 规范化:将旧的或不规范的 URL 重定向到新的规范 URL,有助于 SEO。
- 表单提交后:在服务器端处理完表单提交后,重定向用户到成功页面或列表页面。
注意事项
redirect
只能在服务器端环境中使用(服务器组件、Route Handlers)。- 一旦调用
redirect
,它会立即终止当前请求的渲染,并发送一个重定向响应给客户端。 redirect
会抛出一个特殊的错误,Next.js 会捕获并处理这个错误来执行重定向。因此,在调用redirect
之后,你不需要再返回任何 JSX 或其他内容。
浏览器原生 History API
除了 Next.js 提供的 <Link>
组件和 useRouter
Hook 外,你也可以直接使用浏览器原生的 History API (window.history.pushState
和 window.history.replaceState
) 来操作浏览器历史记录,从而实现页面导航或 URL 更新。这种方式提供了最大的灵活性,但通常需要你手动处理更多的逻辑,例如状态管理和 UI 更新。
window.history.pushState
pushState
方法用于将一个新的状态(state)和 URL 添加到浏览器的历史堆栈中。这意味着用户可以通过浏览器的后退按钮返回到上一个状态。
- 语法 :
window.history.pushState(state, unused, url)
state
: 一个与新历史记录条目关联的状态对象。当用户导航到新状态时,popstate
事件会被触发,并且该状态对象会作为事件的state
属性。unused
: 历史记录的标题。现代浏览器通常会忽略此参数,因此通常传入空字符串或null
。url
: 新的历史记录条目的 URL。如果未指定,则默认为当前 URL。
示例:对列表进行排序
假设你有一个产品列表,并希望通过 URL 查询参数来控制排序方式,但又不想触发完整的页面刷新:
javascript
'use client'
import { useSearchParams } from 'next/navigation'
export default function SortProducts() {
const searchParams = useSearchParams()
function updateSorting(sortOrder) {
const params = new URLSearchParams(searchParams.toString())
params.set('sort', sortOrder)
// 使用 pushState 更新 URL,但不刷新页面
window.history.pushState(null, '', `?${params.toString()}`)
}
return (
<>
<button onClick={() => updateSorting('asc')}>Sort Ascending</button>
<button onClick={() => updateSorting('desc')}>Sort Descending</button>
</>
)
}
点击按钮后,URL 会变为 ?sort=asc
或 ?sort=desc
,但页面不会重新加载,你可以根据 searchParams
的变化来更新 UI。
window.history.replaceState
replaceState
方法用于修改当前历史记录条目的状态和 URL,而不是添加新的条目。这意味着用户无法通过浏览器后退按钮返回到修改前的状态。
- 语法 :
window.history.replaceState(state, unused, url)
- 参数与
pushState
相同。
- 参数与
示例:切换地域设置(国际化)
在国际化应用中,你可能希望用户切换语言时,URL 发生变化,但又不希望用户能够通过后退按钮回到旧的语言版本:
javascript
'use client'
import { usePathname } from 'next/navigation'
export default function LocaleSwitcher() {
const pathname = usePathname()
function switchLocale(locale) {
// e.g. '/en/about' or '/fr/contact'
const newPath = `/${locale}${pathname}`
// 使用 replaceState 替换当前 URL,用户无法后退到旧语言版本
window.history.replaceState(null, '', newPath)
}
return (
<>
<button onClick={() => switchLocale('en')}>English</button>
<button onClick={() => switchLocale('fr')}>French</button>
</>
)
}
注意事项
- 手动处理 UI 更新 :使用 History API 更改 URL 不会自动触发 React 组件的重新渲染。你需要结合
usePathname
、useSearchParams
或其他状态管理方案来监听 URL 变化并手动更新 UI。 - 状态管理 :当使用
pushState
或replaceState
时,你需要自行管理与 URL 状态相关的组件状态。 - 不推荐作为主要导航方式 :对于 Next.js 应用中的常规页面导航,强烈推荐使用
<Link>
组件和useRouter
Hook,因为它们提供了内置的优化和更好的开发体验。History API 更适用于需要精细控制 URL 但不涉及完整页面导航的场景,例如 URL 参数过滤、模态框状态等。
总结
本篇文章详细介绍了 Next.js 15 中实现路由和链接导航的四种主要方式:<Link>
组件、useRouter
Hook、redirect
函数以及浏览器原生的 History API。探讨了每种方式的基础用法、高级特性、适用场景以及背后的原理。
<Link>
组件 :作为 Next.js 推荐的导航方式,它通过预获取、流式渲染和客户端过渡等机制,提供了极致流畅的用户体验。掌握其href
、prefetch
、scroll
等属性,能让你轻松构建高性能的页面跳转。useRouter
Hook :为客户端组件提供了编程式导航的能力,通过push
、replace
、back
、forward
和refresh
等方法,你可以灵活控制页面跳转逻辑,并获取当前的路由信息。redirect
函数:作为服务器端重定向的利器,它在服务器组件或 Route Handlers 中发挥作用,适用于认证、权限控制和 URL 规范化等场景,确保用户在渲染前就被导向正确的位置。- 浏览器原生 History API :虽然不如 Next.js 内置方案便捷,但
pushState
和replaceState
提供了对浏览器历史记录最底层的控制,适用于需要精细化 URL 操作而不触发完整页面刷新的特定场景。