链接与导航

在 Next.js 中有两种方式实现路由间导航:

本文将介绍如何使用 <Link>useRouter(),并深入探讨导航的工作原理。

<Link> 是内置组件,它扩展了 HTML <a> 标签的功能,提供预加载和客户端路由导航。这是 Next.js 中实现路由导航的主要方式。

通过从 next/link 导入该组件,并向其传递 href 属性即可使用:

import Link from 'next/link'

export default function Page() {
  return <Link href="/dashboard">Dashboard</Link>
}
import Link from 'next/link'

export default function Page() {
  return <Link href="/dashboard">Dashboard</Link>
}

你还可以向 <Link> 传递其他可选属性。详见 API 参考文档

示例

链接到动态段

当链接到动态段时,可以使用模板字面量和插值生成链接列表。例如生成博客文章列表:

app/blog/PostList.js
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>
  )
}

检查活动链接

可以使用 usePathname() 判断链接是否处于活动状态。例如,要为活动链接添加类名,可以检查当前 pathname 是否与链接的 href 匹配:

'use client'

import { usePathname } from 'next/navigation'
import Link from 'next/link'

export function Links() {
  const pathname = usePathname()

  return (
    <nav>
      <ul>
        <li>
          <Link className={`link ${pathname === '/' ? 'active' : ''}`} href="/">
            首页
          </Link>
        </li>
        <li>
          <Link
            className={`link ${pathname === '/about' ? 'active' : ''}`}
            href="/about"
          >
            关于
          </Link>
        </li>
      </ul>
    </nav>
  )
}
'use client'

import { usePathname } from 'next/navigation'
import Link from 'next/link'

export function Links() {
  const pathname = usePathname()

  return (
    <nav>
      <ul>
        <li>
          <Link className={`link ${pathname === '/' ? 'active' : ''}`} href="/">
            首页
          </Link>
        </li>
        <li>
          <Link
            className={`link ${pathname === '/about' ? 'active' : ''}`}
            href="/about"
          >
            关于
          </Link>
        </li>
      </ul>
    </nav>
  )
}

滚动到特定 id

Next.js App Router 的默认行为是滚动到新路由的顶部,或在前进/后退导航时保持滚动位置。

如果需要在导航时滚动到特定 id,可以在 URL 后添加 # 哈希链接,或将哈希链接传递给 href 属性。这是可行的,因为 <Link> 会渲染为 <a> 元素。

<Link href="/dashboard#settings">设置</Link>

// 输出
<a href="/dashboard#settings">设置</a>

禁用滚动恢复

Next.js App Router 默认会在新路由中滚动到顶部或在前进/后退导航时保持滚动位置。如需禁用此行为,可以向 <Link> 组件传递 scroll={false},或向 router.push()router.replace() 传递 scroll: false

// next/link
<Link href="/dashboard" scroll={false}>
  仪表盘
</Link>
// useRouter
import { useRouter } from 'next/navigation'

const router = useRouter()

router.push('/dashboard', { scroll: false })

useRouter() 钩子

useRouter 钩子允许你以编程方式更改路由。

此钩子只能在客户端组件中使用,需从 next/navigation 导入。

app/page.js
'use client'

import { useRouter } from 'next/navigation'

export default function Page() {
  const router = useRouter()

  return (
    <button type="button" onClick={() => router.push('/dashboard')}>
      仪表盘
    </button>
  )
}

有关 useRouter 方法的完整列表,请参阅 API 参考文档

建议: 除非有特殊需求,否则应优先使用 <Link> 组件进行路由导航。

路由与导航工作原理

App Router 采用混合方式实现路由和导航。在服务端,应用代码会按路由段自动进行代码分割;在客户端,Next.js 会预加载缓存路由段。这意味着当用户导航到新路由时,浏览器不会重新加载页面,只有发生变更的路由段会重新渲染——从而提升导航体验和性能。

1. 预加载

预加载是一种在用户访问前提前在后台加载路由的方式。

Next.js 中有两种预加载路由的方式:

  • <Link> 组件:当路由出现在用户视口中时自动预加载。预加载发生在页面首次加载或通过滚动进入视图时。
  • router.prefetch():可使用 useRouter 钩子以编程方式预加载路由。

<Link> 的预加载行为对静态路由和动态路由有所不同:

  • 静态路由prefetch 默认为 true。整个路由会被预加载并缓存。
  • 动态路由prefetch 默认为自动模式。仅预加载并缓存共享布局直到第一个 loading.js 文件,缓存时间为 30 秒。这降低了加载整个动态路由的开销,意味着你可以展示即时加载状态以提供更好的视觉反馈。

你可以通过将 prefetch 属性设为 false 来禁用预加载。

更多信息请参阅 <Link> API 参考文档

须知:

  • 预加载功能仅在生产环境中启用,开发环境中不生效。

2. 缓存

Next.js 有一个称为路由缓存 (Router Cache)内存客户端缓存。当用户在应用中导航时,预加载的路由段和已访问路由的 React 服务端组件负载会被存储在缓存中。

这意味着在导航时,系统会尽可能复用缓存,而不是向服务器发起新请求——通过减少请求次数和传输数据量来提升性能。

详细了解路由缓存的工作原理及如何配置。

3. 部分渲染

部分渲染意味着在客户端导航时,只有发生变更的路由段会重新渲染,共享的段则会保持不变。

例如,在 /dashboard/settings/dashboard/analytics 这两个同级路由间导航时,settingsanalytics 页面会被渲染,而共享的 dashboard 布局会保持不变。

部分渲染工作原理

如果没有部分渲染,每次导航都会导致服务器重新渲染整个页面。仅渲染变更的段可以减少数据传输量和执行时间,从而提高性能。

4. 软导航

默认情况下,浏览器会在页面间执行硬导航。这意味着浏览器会重新加载页面并重置 React 状态(如应用中的 useState 钩子)和浏览器状态(如用户的滚动位置或聚焦元素)。然而在 Next.js 中,App Router 使用软导航。这意味着 React 只会渲染发生变更的段,同时保持 React 和浏览器状态不变,且不会重新加载整个页面。

5. 前进与后退导航

默认情况下,Next.js 会在前进和后退导航时保持滚动位置,并复用路由缓存中的路由段。