prefetch根本不是带宽调度机制,它仅在浏览器空闲时以Low优先级被动发起请求,无并发控制、优先级抢占或进度监听能力;大型路由预取需动态注入+条件判断,静态批量声明易造成流量浪费与缓存污染。

prefetch 根本不调度流量,只等空闲时“顺手拉一下”
它没有带宽分配权、不参与当前页资源竞争、不支持并发控制——所谓“流量调度”,是开发者一厢情愿的误读。浏览器只在 window.onload 触发、主线程空闲、网络无高优请求(如 preload、同步脚本)时,才以 Priority: Low 发起请求。这意味着:即使你写了 20 个 <link rel="prefetch">,Chrome 通常只并发发 1–2 个,其余排队或直接丢弃;Safari 可能一个都不发;Firefox 默认关闭(network.prefetch-next = false)。
静态批量声明 <link rel="prefetch"> 是流量浪费高危操作
硬编码几十个路由 JS 的 prefetch 到 <head>,等于强制用户下载所有可能路径的资源,不管是否访问。尤其危险的是预取 HTML(as="document"):它会连带触发该页面内所有 <script>、<link rel="stylesheet"> 的加载,实际流量远超预期。移动端慢网下,几秒内就可能消耗 1–2 MB 流量,而用户根本没点进去。
- 服务端返回动态 nonce/token 的 HTML,缓存后跳转渲染直接失败
- 构建工具(如 Webpack/Vite)若未配
publicPath或base,生成的路径常为相对路径(如./js/detail.js),子路由下解析失败 - Chrome DevTools Network 面板默认不显示 prefetch 请求,需手动勾选 “prefetch” 过滤器才能确认是否发出
真正可控的预取必须动态注入 + 行为触发
用户有明确意图时才发起请求,才是可测、可退、可兜底的做法。关键不是“要不要 prefetch”,而是“什么时候、对谁、怎么发”。
- 触发时机优先选
mouseenter(卡片悬停)、IntersectionObserver(导航项进入视口),延迟 200ms 防误触 - 用
document.createElement('link')动态创建,而非静态写死——这样能做条件判断(如检查navigator.onLine、localStorage.getItem('skip-prefetch')) -
href必须为绝对路径或根相对路径(如/assets/DetailPage.abcd.js),不能是./detail.js或协议相对地址//cdn.example.com/xxx.js -
as属性不可省略且必须准确:as="script"(JS chunk)、as="style"(CSS)、as="document"(HTML 页面)
比 prefetch 更简单可靠的替代方案是 import()
如果你的路由已做代码分割(如 React 的 lazy(() => import('./DetailPage'))),直接调用这个 import() 表达式,浏览器就会自动预取对应 chunk。它本质是 runtime prefetch,优先级略高于 rel="prefetch",且天然兼容所有现代浏览器,无需操心路径、as、同源限制等问题。
立即学习“前端免费学习笔记(深入)”;
真正难的从来不是“怎么写 prefetch”,而是判断用户下一步大概率去哪——这需要埋点、AB 测试、转化漏斗分析,而不是靠堆一堆静态 link 标签。



















