跳到正文

工作收集/React

GitHub

按导航意图启用 Link 预取

ReactNext.jsPrefetch性能

场景

详情页入口出现在长列表中时,Next.js <Link> 的默认视口预取会让一批可见链接提前请求目标路由。对需要读取详情数据或动态生成页面的路由来说,这些请求可能带来并未转化为真实访问的工作。

这里不完全关闭预取,而是把触发时机从“链接进入视口”推迟到“用户表现出导航意图”:鼠标移入,或通过键盘让链接获得焦点。

代码

type HoverPrefetchLinkProps = Omit<ComponentProps<typeof Link>, 'prefetch'>;
 
export function HoverPrefetchLink({
  onMouseEnter,
  onFocus,
  ...props
}: HoverPrefetchLinkProps) {
  const [hasNavigationIntent, setHasNavigationIntent] = useState(false);
 
  const enablePrefetch = () => {
    setHasNavigationIntent(true);
  };
 
  return (
    <Link
      {...props}
      prefetch={hasNavigationIntent ? null : false}
      onMouseEnter={(event) => {
        enablePrefetch();
        onMouseEnter?.(event);
      }}
      onFocus={(event) => {
        enablePrefetch();
        onFocus?.(event);
      }}
    />
  );
}

记一下

这个组件的关键不是“悬停时手动调用预取”,而是分两个阶段控制 <Link>:初始的 false 关闭预取,识别到意图后再用 null 恢复 App Router 的默认行为。

null 不是“强制完整预取”。在 App Router 中,它表示自动策略:静态路由通常完整预取,动态路由会根据路由和 loading 边界决定预取范围。这样既保留框架自己的策略,也避免列表刚出现时就同时预取大量详情页。

状态只会从 false 变成 true,不会在 onMouseLeave 时重置。用户一旦对某个链接表现过意图,这个链接在后续渲染中就继续保持可预取,避免鼠标轻微移出后重复切换状态。

onFocus 与 onMouseEnter 同样重要:前者覆盖键盘导航,不能只为鼠标用户优化。当前实现没有为触屏单独增加预取窗口;触屏点击仍然可以正常导航,只是通常会直接进入目标页。

这套语义针对 Next.js App Router。预取只在生产环境启用,因此验证网络请求时应使用生产构建,而不是只看开发模式。

示例直接使用 next/link,方便独立检查。如果项目使用国际化导航等自定义 Link 包装器,只要它继续透传 Next.js Link 的 prefetch 与事件 Props,也可以沿用同样的控制方式。

我做了什么

  • 首次渲染传入 prefetch={false},阻止长列表里的链接因为进入视口而批量预取。
  • 收到 onMouseEnter 或 onFocus 后记录导航意图。
  • 下一次渲染把 prefetch 恢复为 null,重新启用 App Router 的默认预取策略。
  • 继续调用外部传入的同名事件处理函数,不改变包装前的交互能力。
  • 从组件 Props 中排除 prefetch,让预取策略只由这个组件管理。

核心流程

链接进入视口
  ↓
prefetch=false,不发起默认预取
  ↓
鼠标移入 / 键盘聚焦
  ↓
hasNavigationIntent=true
  ↓
重新渲染为 prefetch=null
  ↓
恢复 Next.js 默认预取策略