真正可用的方案是行为触发+动态注入+限定资源类型;静态写死不可控,尤其预取html风险大,需同时满足onload完成、浏览器空闲、页面未卸载三个条件,且as属性必须显式指定并匹配资源类型。

静态写死 <link rel="prefetch"> 基本不可控,尤其预取 HTML 文档时风险远大于收益;真正可用的方案是行为触发 + 动态注入 + 限定资源类型。
为什么 <link rel="prefetch" href="/next.html"> 经常没效果
它依赖三个硬性前提同时满足:页面已触发 onload、浏览器当前空闲(无高优先级请求、CPU/网络负载低)、页面尚未卸载。任意一个不成立,请求就不会发出——不是代码错了,是条件没凑齐。Chrome DevTools Network 面板默认不显示 prefetch 请求,得手动勾选 prefetch 过滤器或清空所有过滤器才能看到。Safari 在 iOS 16.4 之前基本忽略 as="document",写了也白写。更隐蔽的问题是:即使请求发了,返回头若缺少 Cache-Control: public, max-age=31536000,后续跳转时缓存无法复用,prefetch 完全白做。
as 属性必须显式指定,且不能乱填
浏览器不会自动推断资源类型,漏掉 as 或填错,会导致降级为 as="fetch",甚至被 Safari 静默丢弃。不同资源对应不同值:
- 预取 JS chunk(如 Vite 构建后的
/assets/DetailPage.xxxx.js)→as="script" - 预取分离的 CSS →
as="style" - 预取 HTML 页面(不推荐)→
as="document",但需确保服务端返回Content-Type: text/html,且无动态nonce或 token,否则缓存后执行失败 - 跨域字体等资源 → 必须加
crossorigin,否则静默失败
动态注入 <link rel="prefetch"> 才可控
静态写死在 里等于“一锤定音”,用户还没动就开下,流量、缓存、兼容性全失控。动态注入才能按需、可退、可测:
- 用
document.createElement('link')创建,显式设置rel="prefetch"、as、href(建议绝对路径或根相对路径,如/assets/DetailPage.xxxx.js) - 触发时机选
mouseenter(加 200ms 延迟防误触)或IntersectionObserver(元素进入可视区) - 在 React 中放在
useEffect里,在 Vue 中放在onMounted里,避免 SSR 渲染时报错 - 只预取确定路径的资源,别写
/detail这种路由地址——它可能返回 301/302,也可能服务端渲染出带 nonce 的 HTML,导致缓存失效
比 prefetch 更简单可靠的替代方案是 import()
如果你的“下一页”是代码分割模块(比如 React 的 lazy(() => import('./DetailPage'))),直接调用这个 import() 表达式就行。浏览器会自动发起 runtime prefetch,优先级略高于 rel="prefetch",且所有现代浏览器都支持,无需操心 as、crossorigin、缓存头这些细节。它天然绑定用户意图:只有组件准备挂载时才触发,不会提前下载、不会污染缓存、不会在用户切页后还在后台跑。
最容易被忽略的一点:prefetch 不是“提前执行”,它只是提前下载并存进 HTTP 缓存。你看到 Network 里有请求,不代表后续跳转时真用上了——得去目标页的 Network 面板确认关键资源 Size 列是否显示 from disk cache。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











