Next.js 路由导航:四种方式构建流畅的页面跳转(三)

前言

在前端 Web 开发中,路由和导航系统是构建用户友好体验的关键部分。Next.js 15 为开发者提供了一套完整而强大的路由解决方案,不仅支持 声明式和命令式 的导航方式,还内置了 预加载、流式渲染 等性能优化机制,让我们能够轻松打造出流畅自然的页面跳转体验。通过合理运用这套路由系统,我们可以有效管理应用状态,提升用户体验。

本文介绍 Next.js 中的四大导航机制:

  1. <Link> 组件:Next.js 推荐的导航方式,支持预加载和客户端导航
  2. useRouter Hook:用于客户端组件中的编程式导航
  3. redirect 函数:专门用于服务端组件的重定向
  4. 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}

    javascript 复制代码
    import 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.jsSuspense)。

  • 结合 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(pushStatereplaceState)来改变 URL,并结合 React 的组件渲染机制,只更新发生变化的组件部分,而不是重新渲染整个 DOM 树。

  • 优势

    • 速度快:无需重新加载整个页面,只更新必要部分,响应速度更快。
    • 用户体验好:页面切换平滑,没有闪烁或白屏。
    • 状态保持:在某些情况下,可以更好地保持页面状态(例如滚动位置、表单输入)。

跳转行为设置

App Router 的默认行为是滚动到新路由的顶部,或者在前进后退导航时维持之前的滚动距离。如果你想要禁用这个行为,可以给 <Link> 组件传递一个 scroll={false} 属性:

javascript 复制代码
// next/link
<Link href="/dashboard" scroll={false}>
  Dashboard
</Link>

或者在使用 router.pushrouter.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 来禁用滚动到顶部。

    javascript 复制代码
    router.push('/posts/123', { scroll: false });
  • router.replace(href, options): 替换历史堆栈中的当前路由,用户无法通过浏览器后退按钮返回上一个页面。options 同样可以包含 scroll: false

    javascript 复制代码
    router.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.pushStatewindow.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 组件的重新渲染。你需要结合 usePathnameuseSearchParams 或其他状态管理方案来监听 URL 变化并手动更新 UI。
  • 状态管理 :当使用 pushStatereplaceState 时,你需要自行管理与 URL 状态相关的组件状态。
  • 不推荐作为主要导航方式 :对于 Next.js 应用中的常规页面导航,强烈推荐使用 <Link> 组件和 useRouter Hook,因为它们提供了内置的优化和更好的开发体验。History API 更适用于需要精细控制 URL 但不涉及完整页面导航的场景,例如 URL 参数过滤、模态框状态等。

总结

本篇文章详细介绍了 Next.js 15 中实现路由和链接导航的四种主要方式:<Link> 组件、useRouter Hook、redirect 函数以及浏览器原生的 History API。探讨了每种方式的基础用法、高级特性、适用场景以及背后的原理。

  • <Link> 组件 :作为 Next.js 推荐的导航方式,它通过预获取、流式渲染和客户端过渡等机制,提供了极致流畅的用户体验。掌握其 hrefprefetchscroll 等属性,能让你轻松构建高性能的页面跳转。
  • useRouter Hook :为客户端组件提供了编程式导航的能力,通过 pushreplacebackforwardrefresh 等方法,你可以灵活控制页面跳转逻辑,并获取当前的路由信息。
  • redirect 函数:作为服务器端重定向的利器,它在服务器组件或 Route Handlers 中发挥作用,适用于认证、权限控制和 URL 规范化等场景,确保用户在渲染前就被导向正确的位置。
  • 浏览器原生 History API :虽然不如 Next.js 内置方案便捷,但 pushStatereplaceState 提供了对浏览器历史记录最底层的控制,适用于需要精细化 URL 操作而不触发完整页面刷新的特定场景。
相关推荐
前端大卫1 小时前
Vue3 + Element-Plus 自定义虚拟表格滚动实现方案【附源码】
前端
却尘2 小时前
Next.js 请求最佳实践 - vercel 2026一月发布指南
前端·react.js·next.js
ccnocare2 小时前
浅浅看一下设计模式
前端
Lee川2 小时前
🎬 从标签到屏幕:揭秘现代网页构建与适配之道
前端·面试
Ticnix2 小时前
ECharts初始化、销毁、resize 适配组件封装(含完整封装代码)
前端·echarts
纯爱掌门人2 小时前
终焉轮回里,藏着 AI 与人类的答案
前端·人工智能·aigc
twl2 小时前
OpenClaw 深度技术解析
前端
崔庆才丨静觅2 小时前
比官方便宜一半以上!Grok API 申请及使用
前端
星光不问赶路人3 小时前
vue3使用jsx语法详解
前端·vue.js
天蓝色的鱼鱼3 小时前
shadcn/ui,给你一个真正可控的UI组件库
前端