Skip to content

预请求

¥Prefetching

预取功能使应用中不同路由之间的导航变得即时。Next.js 默认会根据应用代码中使用的链接进行智能预加载。

¥Prefetching makes navigating between different routes in your application feel instant. Next.js tries to intelligently prefetch by default, based on the links used in your application code.

本指南将讲解预取的工作原理,并展示常见的实现模式:

¥This guide will explain how prefetching works and show common implementation patterns:

预取如何工作?

¥How does prefetching work?

在路由之间导航时,浏览器会请求页面的资源,例如 HTML 和 JavaScript 文件。预取是指在导航到新路由之前提前获取这些资源的过程。

¥When navigating between routes, the browser requests assets for the page like HTML and JavaScript files. Prefetching is the process of fetching these resources ahead of time, before you navigate to a new route.

Next.js 会根据路由自动将应用拆分为更小的 JavaScript 代码块。与传统 SPA 不同,它不会预先加载所有代码,而是只加载当前路由所需的代码。这会减少初始加载时间,同时应用的其他部分会在后台加载。当你点击链接时,新路由的资源已被加载到浏览器缓存中。

¥Next.js automatically splits your application into smaller JavaScript chunks based on routes. Instead of loading all the code upfront like traditional SPAs, only the code needed for the current route is loaded. This reduces the initial load time while other parts of the app are loaded in the background. By the time you click the link, the resources for the new route have already been loaded into the browser cache.

导航到新页面时,不会出现整个页面重新加载或浏览器加载旋转窗口。相反,Next.js 会执行 客户端转换,使页面导航感觉即时。

¥When navigating to the new page, there's no full page reload or browser loading spinner. Instead, Next.js performs a client-side transition, making the page navigation feel instant.

预取静态路由与动态路由

¥Prefetching static vs. dynamic routes

静态页面动态页面
已预取是,完整路由否,除非 loading.js
客户端缓存 TTL5 分钟(默认)关闭,除非 enabled
点击时服务器往返是,在 shell 之后进行流式传输

需要了解:在初始导航期间,浏览器会获取 HTML、JavaScript 和 React 服务器组件 (RSC) 负载。对于后续导航,浏览器将获取服务器组件的 RSC Payload 和客户端组件的 JS 包。

¥Good to know: During the initial navigation, the browser fetches the HTML, JavaScript, and React Server Components (RSC) Payload. For subsequent navigations, the browser will fetch the RSC Payload for Server Components and JS bundle for Client Components.

自动预取

¥Automatic prefetch

tsx
import Link from 'next/link'

export default function NavLink() {
  return <Link href="/about">About</Link>
}
上下文预取有效负载客户端缓存 TTL
loading.js整个页面直到应用重新加载
使用 loading.js布局到第一个加载边界30 秒 (configurable)

自动预取仅在生产环境中运行。使用 prefetch={false} 禁用或在 禁用预取 中使用封装器。

¥Automatic prefetching runs only in production. Disable with prefetch={false} or use the wrapper in Disabled Prefetch.

手动预取

¥Manual prefetch

tsx
'use client'

import { useRouter } from 'next/navigation'

const router = useRouter()
router.prefetch('/pricing')

调用 router.prefetch() 来预热视口外的路由,或响应分析、悬停、滚动等操作。

¥Call router.prefetch() to warm routes outside the viewport or in response to analytics, hover, scroll, etc.

悬停触发预取

¥Hover-triggered prefetch

请谨慎操作:扩展 Link 会让你保留预取、缓存失效和可访问性问题。仅在默认值不足时继续。

¥Proceed with caution: Extending Link opts you into maintaining prefetching, cache invalidation, and accessibility concerns. Proceed only if defaults are insufficient.

Next.js 默认会尝试正确进行预加载,但高级用户可以根据需要进行移除和修改。你可以控制性能和资源消耗。

¥Next.js tries to do the right prefetching by default, but power users can eject and modify based on their needs. You have the control between performance and resource consumption.

例如,你可能只需要在鼠标悬停时触发预取,而不是在进入视口时(默认行为):

¥For example, you might have to only trigger prefetches on hover, instead of when entering the viewport (the default behavior):

tsx
'use client'

import Link from 'next/link'
import { useState } from 'react'

export function HoverPrefetchLink({
  href,
  children,
}: {
  href: string
  children: React.ReactNode
}) {
  const [active, setActive] = useState(false)

  return (
    <Link
      href={href}
      prefetch={active ? null : false}
      onMouseEnter={() => setActive(true)}
    >
      {children}
    </Link>
  )
}

一旦用户显示意图,prefetch={null} 就会恢复默认(静态)预取。

¥prefetch={null} restores default (static) prefetching once the user shows intent.

¥Extending or ejecting link

你可以扩展 <Link> 组件以创建自己的自定义预取策略。例如,使用 ForesightJS 库,该库通过预测用户光标的方向来预取链接。

¥You can extend the <Link> component to create your own custom prefetching strategy. For example, using the ForesightJS library which prefetches links by predicting the direction of the user's cursor.

或者,你可以使用 useRouter 来重现一些原生 <Link> 行为。但是,请注意,这将导致你选择保留预取和缓存失效。

¥Alternatively, you can use useRouter to recreate some of the native <Link> behavior. However, be aware this opts you into maintaining prefetching and cache invalidation.

tsx
'use client'

import { useRouter } from 'next/navigation'
import { useEffect } from 'react'

function ManualPrefetchLink({
  href,
  children,
}: {
  href: string
  children: React.ReactNode
}) {
  const router = useRouter()

  useEffect(() => {
    let cancelled = false
    const poll = () => {
      if (!cancelled) router.prefetch(href, { onInvalidate: poll })
    }
    poll()
    return () => {
      cancelled = true
    }
  }, [href, router])

  return (
    <a
      href={href}
      onClick={(event) => {
        event.preventDefault()
        router.push(href)
      }}
    >
      {children}
    </a>
  )
}

当 Next.js 怀疑缓存数据已过时时,会调用 onInvalidate,以便刷新预取数据。

¥onInvalidate is invoked when Next.js suspects cached data is stale, allowing you to refresh the prefetch.

需要了解:使用 a 标签会导致页面导航到目标路由,你可以使用 onClick 阻止页面导航,然后调用 router.push 导航到目标路由。

¥Good to know: Using an a tag will cause a full page navigation to the destination route, you can use onClick to prevent the full page navigation, and then invoke router.push to navigate to the destination.

已禁用预取

¥Disabled prefetch

你可以完全禁用某些路由的预取,以便更精细地控制资源消耗。

¥You can fully disable prefetching for certain routes for more fine-grained control over resource consumption.

tsx
'use client'

import Link, { LinkProps } from 'next/link'

function NoPrefetchLink({
  prefetch,
  ...rest
}: LinkProps & { children: React.ReactNode }) {
  return <Link {...rest} prefetch={false} />
}

例如,你可能仍希望在应用中一致地使用 <Link>,但页脚中的链接在进入视口时可能不需要预取。

¥For example, you may still want to have consistent usage of <Link> in your application, but links in your footer might not need to be prefetched when entering the viewport.

预取优化

¥Prefetching optimizations

需要了解:布局去重和预取调度是即将进行的优化的一部分。目前在 Next.js 预览发布中可通过 experimental.clientSegmentCache 标志使用。

¥Good to know: Layout deduplication and prefetch scheduling are part of upcoming optimizations. Currently available in Next.js canary via the experimental.clientSegmentCache flag.

客户端缓存

¥Client cache

Next.js 将预取的 React 服务器组件负载存储在内存中,并以路由段为键。在同级路由(例如 /dashboard/settings/dashboard/analytics)之间导航时,浏览器会重用父级布局并仅获取更新的叶子页面。这可以减少网络流量并提高导航速度。

¥Next.js stores prefetched React Server Component payloads in memory, keyed by route segments. When navigating between sibling routes (e.g. /dashboard/settings/dashboard/analytics), it reuses the parent layout and only fetches the updated leaf page. This reduces network traffic and improves navigation speed.

预取调度

¥Prefetch scheduling

Next.js 维护一个小型任务队列,该队列按以下顺序预取:

¥Next.js maintains a small task queue, which prefetches in the following order:

  1. 视口中的链接

    ¥Links in the viewport

  2. 显示用户意图(悬停或触摸)的链接

    ¥Links showing user intent (hover or touch)

  3. 较新的链接将替换较旧的链接

    ¥Newer links replace older ones

  4. 滚动到屏幕外的链接将被丢弃

    ¥Links scrolled off-screen are discarded

调度程序会优先考虑可能的导航,同时最大限度地减少未使用的下载。

¥The scheduler prioritizes likely navigations while minimizing unused downloads.

部分预渲染 (PPR)

¥Partial Prerendering (PPR)

启用 PPR 后,页面将分为静态 shell 和流式动态部分:

¥When PPR is enabled, a page is divided into a static shell and a streamed dynamic section:

  • shell 可以预取,并立即进行流式传输。

    ¥The shell, which can be prefetched, streams immediately

  • 动态数据流在准备就绪后传输

    ¥Dynamic data streams when ready

  • 数据失效(revalidateTagrevalidatePath)会静默刷新相关的预取

    ¥Data invalidations (revalidateTag, revalidatePath) silently refresh associated prefetches

故障排除

¥Troubleshooting

在执行过程中触发不必要的副作用预取

¥Triggering unwanted side-effects during prefetching

如果你的布局或页面不是 pure 并且有副作用(例如跟踪分析),这些副作用可能会在路由预取时触发,而不是在用户访问页面时。

¥If your layouts or pages are not pure and have side-effects (e.g. tracking analytics), these might be triggered when the route is prefetched, not when the user visits the page.

为避免这种情况,你应该将副作用移至 useEffect 钩子或从客户端组件触发的服务器操作。

¥To avoid this, you should move side-effects to a useEffect hook or a Server Action triggered from a Client Component.

之前:

¥Before:

tsx
import { trackPageView } from '@/lib/analytics'

export default function Layout({ children }: { children: React.ReactNode }) {
  // This runs during prefetch
  trackPageView()

  return <div>{children}</div>
}

之后:

¥After:

tsx
'use client'

import { useEffect } from 'react'
import { trackPageView } from '@/lib/analytics'

export function AnalyticsTracker() {
  useEffect(() => {
    trackPageView()
  }, [])

  return null
}
tsx
import { AnalyticsTracker } from '@/app/ui/analytics-tracker'

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <div>
      <AnalyticsTracker />
      {children}
    </div>
  )
}

防止过多预取

¥Preventing too many prefetches

使用 <Link> 组件时,Next.js 会自动预加载视口中的链接。

¥Next.js automatically prefetches links in the viewport when using the <Link> component.

在某些情况下,你可能需要阻止这种情况,以避免不必要的资源使用,例如在渲染大量链接列表(例如无限滚动表)时。

¥There may be cases where you want to prevent this to avoid unnecessary usage of resources, such as when rendering a large list of links (e.g. an infinite scroll table).

你可以通过将 <Link> 组件的 prefetch 属性设置为 false 来禁用预取。

¥You can disable prefetching by setting the prefetch prop of the <Link> component to false.

tsx
<Link prefetch={false} href={`/blog/${post.id}`}>
  {post.title}
</Link>

但是,这意味着静态路由仅在点击时才会被获取,而动态路由将等待服务器渲染后再进行导航。

¥However, this means static routes will only be fetched on click, and dynamic routes will wait for the server to render before navigating.

为减少资源使用量而不完全禁用预取功能,你可以将预取功能推迟到用户将鼠标悬停在链接上时执行。这仅针对用户可能访问的链接。

¥To reduce resource usage without disabling prefetch entirely, you can defer prefetching until the user hovers over a link. This targets only links the user is likely to visit.

tsx
'use client'

import Link from 'next/link'
import { useState } from 'react'

export function HoverPrefetchLink({
  href,
  children,
}: {
  href: string
  children: React.ReactNode
}) {
  const [active, setActive] = useState(false)

  return (
    <Link
      href={href}
      prefetch={active ? null : false}
      onMouseEnter={() => setActive(true)}
    >
      {children}
    </Link>
  )
}