预取功能使您应用中不同路由之间的导航体验瞬间完成。Next.js 默认会根据应用代码中使用的链接智能地进行预取。
本指南将解释预取的工作原理,并展示常见的实现模式:
当在路由之间导航时,浏览器会请求页面的资源,例如 HTML 和 JavaScript 文件。预取是在您导航到新路由之前,_提前_获取这些资源的过程。
Next.js 会根据路由自动将您的应用拆分为更小的 JavaScript 块。与传统 SPA 一次性加载所有代码不同,它只加载当前路由所需的代码。这减少了初始加载时间,同时应用的其他部分在后台加载。当您点击链接时,新路由的资源已经加载到浏览器缓存中。
导航到新页面时,不会出现整页重新加载或浏览器加载指示器。相反,Next.js 会执行客户端过渡,使页面导航感觉瞬间完成。
| 静态页面 | 动态页面 | |
|---|---|---|
| 是否预取 | 是,完整路由 | 否,除非存在 loading.js |
| 客户端缓存 TTL | 5 分钟 (默认) | 否,除非启用 |
| 点击时服务器往返 | 否 | 是,在外壳之后流式传输 |
须知: 在首次导航期间,浏览器会获取 HTML、JavaScript 和 React 服务器组件 (RSC) Payload。对于后续导航,浏览器将为服务器组件获取 RSC Payload,并为客户端组件获取 JS bundle。
import Link from 'next/link'
export default function NavLink() {
return <Link href="/about">About</Link>
}import Link from 'next/link'
export default function NavLink() {
return <Link href="/about">About</Link>
}| 上下文 | 预取负载 | 客户端缓存 TTL |
|---|---|---|
无 loading.js | 整个页面 | 直到应用重新加载 |
存在 loading.js | 布局到第一个加载边界 | 30 秒 (可配置) |
自动预取只在生产环境中运行。可以使用 prefetch={false} 禁用,或使用 禁用预取 中的包装器。
要进行手动预取,请从 next/navigation 导入 useRouter hook,并调用 router.prefetch() 来预热视口之外的路由,或响应分析、悬停、滚动等操作。
'use client'
import { useRouter } from 'next/navigation'
import { CustomLink } from '@components/link'
export function PricingCard() {
const router = useRouter()
return (
<div onMouseEnter={() => router.prefetch('/pricing')}>
{/* other UI elements */}
<CustomLink href="/pricing">View Pricing</CustomLink>
</div>
)
}如果目的是在组件加载时预取 URL,请参阅扩展或拒绝链接的[示例]。
谨慎操作: 扩展
<Link>意味着您需要自行维护预取、缓存失效和可访问性问题。仅当默认设置不足时才进行此操作。
Next.js 默认会尝试进行正确的预取,但高级用户可以根据自身需求选择脱离并进行修改。您可以在性能和资源消耗之间进行控制。
例如,您可能需要仅在鼠标悬停时触发预取,而不是在进入视口时(默认行为):
'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} 会恢复默认(静态)预取。
您可以扩展 <Link> 组件来创建自己的自定义预取策略。例如,使用 ForesightJS 库,它通过预测用户光标的方向来预取链接。
另外,您可以使用 useRouter 来重新创建一些原生的 <Link> 行为。但是,请注意,这会使您自行维护预取和缓存失效。
'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,允许您刷新预取。
须知: 使用
a标签会导致完整页面导航到目标路由,您可以使用onClick来阻止完整页面导航,然后调用router.push导航到目标。
您可以完全禁用某些路由的预取,以便对资源消耗进行更精细的控制。
'use client'
import Link, { LinkProps } from 'next/link'
function NoPrefetchLink({
prefetch,
...rest
}: LinkProps & { children: React.ReactNode }) {
return <Link {...rest} prefetch={false} />
}例如,您可能仍希望在应用中保持 <Link> 的一致使用,但页脚中的链接在进入视口时可能不需要预取。
Next.js 将预取的 React 服务器组件 payload 存储在内存中,以路由段作为键。当在同级路由之间导航时(例如 /dashboard/settings → /dashboard/analytics),它会重用父布局,并且只获取更新的叶子页面。这减少了网络流量并提高了导航速度。
Next.js 维护一个小型任务队列,并按以下顺序进行预取:
调度器优先处理可能的导航,同时最大程度地减少未使用的下载。
当启用 PPR 时,页面被分为一个静态外壳和一个流式动态部分:
revalidateTag、revalidatePath)会静默刷新相关的预取如果您的布局或页面不是纯净的并且存在副作用(例如,跟踪分析),那么这些副作用可能会在路由预取时触发,而不是在用户访问页面时触发。
为避免此问题,您应该将副作用移动到 useEffect hook 或从客户端组件触发的 Server Action 中。
之前:
import { trackPageView } from '@/lib/analytics'
export default function Layout({ children }: { children: React.ReactNode }) {
// This runs during prefetch
trackPageView()
return <div>{children}</div>
}import { trackPageView } from '@/lib/analytics'
export default function Layout({ children }) {
// This runs during prefetch
trackPageView()
return <div>{children}</div>
}之后:
'use client'
import { useEffect } from 'react'
import { trackPageView } from '@/lib/analytics'
export function AnalyticsTracker() {
useEffect(() => {
trackPageView()
}, [])
return null
}'use client'
import { useEffect } from 'react'
import { trackPageView } from '@/lib/analytics'
export function AnalyticsTracker() {
useEffect(() => {
trackPageView()
}, [])
return null
}import { AnalyticsTracker } from '@/app/ui/analytics-tracker'
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<div>
<AnalyticsTracker />
{children}
</div>
)
}import { AnalyticsTracker } from '@/app/ui/analytics-tracker'
export default function Layout({ children }) {
return (
<div>
<AnalyticsTracker />
{children}
</div>
)
}使用 <Link> 组件时,Next.js 会自动预取视口中的链接。
在某些情况下,您可能希望阻止此行为,以避免不必要的资源使用,例如在渲染大量链接列表时(例如,无限滚动表格)。
您可以通过将 <Link> 组件的 prefetch 属性设置为 false 来禁用预取。
<Link prefetch={false} href={`/blog/${post.id}`}>
{post.title}
</Link>然而,这意味着静态路由只会在点击时获取,而动态路由在导航前将等待服务器渲染。
为了在不完全禁用预取的情况下减少资源使用,您可以将预取推迟到用户将鼠标悬停在链接上时。这只针对用户可能访问的链接。
'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>
)
}'use client'
import Link from 'next/link'
import { useState } from 'react'
export function HoverPrefetchLink({ href, children }) {
const [active, setActive] = useState(false)
return (
<Link
href={href}
prefetch={active ? null : false}
onMouseEnter={() => setActive(true)}
>
{children}
</Link>
)
}