静态写死 <link rel="prefetch"> 预取HTML极危险:后台持续下载、浪费流量、缓存污染、Safari支持差;应改用行为触发+动态注入+import()运行时预取。
硬写 <link rel="prefetch"> 到 HTML 里预取“下一页”,基本等于在用户不知情时悄悄下载一个可能永远用不到的页面,尤其在移动端极易浪费流量、触发缓存污染、且 Safari/iOS 支持极差——这不是优化,是埋雷。
rel="prefetch" 很危险浏览器一旦发起 prefetch 请求,就不会因为用户切页、关闭标签、刷新而中止;它会在后台持续下载,哪怕当前页面 DOM 已销毁。更麻烦的是:
as="document"(即预取 HTML)风险最高:会连带加载该 HTML 中引用的 JS/CSS/图片,实际消耗远超预期,尤其当服务端返回动态 nonce 或 token 时,缓存后渲染直接失败prefetch 的支持直到 iOS 16.4 才稳定,旧版 WebView 基本忽略,写了等于白写prefetch 的缓存状态——它走的是 HTTP 缓存,不是内存缓存,排查困难用户有明确意图时才预取,才是可控、可测、可退的实践。关键动作包括:
document.createElement('link') 动态插入,而非静态写死 —— 这样可以按需创建、条件判断、错误捕获mouseenter(卡片悬停)或 IntersectionObserver(进入可视区),延迟 200ms 防误触/assets/DetailPage.xxxx.js,别写 /detail 这种 HTML 路由地址preload)useEffect 或 onMounted 里执行,避免 SSR 渲染时报错示例代码:
立即学习“前端免费学习笔记(深入)”;
const prefetchResource = (url) => { const link = document.createElement('link'); link.rel = 'prefetch'; link.as = 'script'; // 显式声明 as,避免降级为 document link.href = url; document.head.appendChild(link);};card.addEventListener('mouseenter', () => { setTimeout(() => prefetchResource('/assets/DetailPage.abcd.js'), 200);});
prefetch 更简单可靠的替代方案:直接调用 import()
如果你的“下一页”是代码分割后的模块(如 React lazy(() => import('./About'))),直接运行这个 import() 表达式,浏览器就会自动预取对应 chunk —— 它本质是 runtime prefetch,优先级略高于 rel="prefetch",且天然兼容所有现代浏览器。
link 标签生命周期import('./About'),比动态注入 prefetch 更语义清晰、调试友好复杂点在于:预取的资源必须有强缓存(Cache-Control: public, max-age=31536000),否则跳转后依然要重新请求;而动态注入的时机、网络条件判断、fallback 逻辑(比如检测 navigator.connection.effectiveType 是 2g 就跳过),才是真正决定效果的关键——这些没法靠一行 <link> 解决。