首页 / Next.js 16 入门教程 / 链接与导航

Next.js 16 入门教程

链接与导航

本教程共 42 篇 · 第 5 篇 · 更新于 2026-07-30 · 约 8 分钟阅读

Next.jsNext.js 16 入门教程导航LinkuseRouter路由

5. 链接与导航

本节目标:学会在 Next.js 页面间跳转的所有方式,理解预取、客户端转换和流式渲染如何让导航飞快。

导航的四个概念

Next.js 的导航优化建立在四个机制上:

  1. 服务端渲染:页面在服务器生成 HTML
  2. 预取:链接进入视口时提前加载
  3. 流式渲染:页面分段发送,不必等全部完成
  4. 客户端转换:跳转时只更新变化的部分,不刷新整个页面

理解这四个概念,才能用好导航 API。

<Link> 是 Next.js 导航的主要方式,替代原生 <a> 标签:

import Link from 'next/link'

export default function Nav() {
  return (
    <nav>
      <Link href="/">首页</Link>
      <Link href="/blog">博客</Link>
      <Link href="/about">关于</Link>
    </nav>
  )
}

跳动态路由

<Link href={`/blog/${post.slug}`}>{post.title}</Link>

带查询参数

<Link href={{
  pathname: '/products',
  query: { category: 'shoes', sort: 'price' },
}}>
  鞋子
</Link>

会生成 /products?category=shoes&sort=price

常用 Props

Prop默认值说明
href必填跳转路径
replacefalse替换当前历史记录,不新增
scrolltrue是否滚动到页面顶部
prefetchnull是否预取(true/false/null
// 不滚动到顶部
<Link href="/dashboard" scroll={false}>仪表盘</Link>

// 替换历史记录(用户无法后退)
<Link href="/success" replace>提交成功</Link>

预取行为

prefetch 控制预取策略:

  • null(默认):静态路由全量预取,动态路由只预取到最近的 loading.tsx
  • true:始终全量预取
  • false:不预取
// 禁用预取(适合大量链接的列表)
<Link href="/blog" prefetch={false}>博客</Link>
Note

预取只在生产环境生效。开发模式下不会预取。

useRouter Hook

需要编程式导航时,用 useRouter

'use client'

import { useRouter } from 'next/navigation'

export function Button() {
  const router = useRouter()

  return (
    <button onClick={() => router.push('/dashboard')}>
      跳转
    </button>
  )
}

常用方法

// 前进到新页面(新增历史记录)
router.push('/blog')

// 替换当前页面(不新增历史记录)
router.replace('/blog')

// 后退
router.back()

// 刷新当前页面(重新获取数据)
router.refresh()

带选项跳转

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

usePathname Hook

获取当前路径,常用于高亮当前导航项:

'use client'

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

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

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

useSearchParams Hook

读取 URL 查询参数:

'use client'

import { useSearchParams } from 'next/navigation'

export function SortButtons() {
  const searchParams = useSearchParams()

  function sort(order: string) {
    const params = new URLSearchParams(searchParams.toString())
    params.set('sort', order)
    window.history.pushState(null, '', `?${params.toString()}`)
  }

  return (
    <>
      <button onClick={() => sort('asc')}>价格升序</button>
      <button onClick={() => sort('desc')}>价格降序</button>
    </>
  )
}

原生 History API

Next.js 支持直接操作浏览器历史栈:

'use client'

// 新增历史记录
window.history.pushState(null, '', '?sort=asc')

// 替换当前记录
window.history.replaceState(null, '', '/en/about')

这些操作会同步更新 Next.js 路由器的状态。

导航优化

动态路由加 loading.tsx

没有 loading.tsx 的动态路由,导航时会白屏等待。加上它,用户立刻看到骨架屏:

// app/blog/[slug]/loading.tsx
export default function Loading() {
  return <div className="animate-pulse">加载中...</div>
}

慢网络下的反馈

网络慢时,预取可能来不及完成。用 useLinkStatus 显示即时反馈:

'use client'

import { useLinkStatus } from 'next/link'

export function LoadingIndicator() {
  const { pending } = useLinkStatus()
  return (
    <span className={pending ? 'visible' : 'invisible'}>
      加载中...
    </span>
  )
}

禁用预取的场景

以下情况考虑禁用预取:

  • 无限滚动列表(链接太多)
  • 权限页面(用户可能没权限访问)
  • 导出/下载链接
<Link href="/heavy-page" prefetch={false}>重页面</Link>

跨根布局导航

如果应用有多个根布局(通过路由组实现),跨根布局导航会触发完整页面加载,而不是客户端转换。

这是因为不同根布局的 <html> 结构可能不同,无法平滑过渡。

小结

这一章我们学了导航的完整方案:

  1. <Link> 组件:声明式导航,自动预取
  2. useRouter:编程式导航,支持 push/replace/back/refresh
  3. usePathname:获取当前路径,高亮导航项
  4. useSearchParams:读取查询参数
  5. loading.tsx:让动态路由导航即时响应

下一章,我们来学习 Next.js 最核心的概念——Server Components。