从 instant.page 迁移到 Speculation Rules API

从 instant.page 迁移到浏览器原生的 Speculation Rules API。本文介绍 prefetch 与 prerender 的区别、适用边界、实际配置,以及它为何与 Swup/PJAX 这类 SPA 导航方案的冲突。

之前本站用 instant.page 优化站内跳转。它的工作方式很好理解:用户 hover 一个链接时,脚本插入 <link rel="prefetch" as="document">,先把下一页 HTML 下载回来。等用户真的点击,浏览器少等一次网络请求,页面就显得更快。

这个方案轻巧,也可靠。只是它仍然是一段第三方脚本,而且它做的事情本质上停在 prefetch:提前拿到目标文档,不能提前把页面跑起来。

现在 Chromium 系浏览器提供了更原生的选择:Speculation Rules API。它允许我们用一段 JSON 告诉浏览器:哪些页面可能会被访问,应该提前准备到什么程度。

兼容性仍需要注意:截至 2026-06,稳定支持主要在 Chromium 这边;Firefox 还不支持;Safari 仍处在偏好开关后的部分支持阶段。所以它只能作为渐进增强,不应该成为页面可用性的前提。

适用边界

Speculation Rules API 面向的是 document navigation,也就是从一个 HTML 文档导航到另一个 HTML 文档。它尤其适合 MPA、静态站、内容站、商品列表到详情页这类页面跳转。

它不是通用的资源预加载工具。MDN 文档里专门提醒过:Speculation Rules API 不负责预取页面里的子资源;如果你想提前加载某个 JS chunk、字体或图片,那仍然是 <link rel="prefetch">preload 或框架自己的资源调度问题。

它也不是强制命令。浏览器会把规则当成一个提示,然后根据网络、内存、电量、用户设置和自身启发式决定要不要执行。不支持的浏览器会忽略 <script type="speculationrules">,所以它适合作为渐进增强。

prefetch 和 prerender

Speculation Rules 里最常用的两种动作是 prefetchprerender

动作浏览器会做什么成本适合什么情况
prefetch提前请求目标文档,不加载目标页子资源,不执行目标页脚本想安全替代 instant.page
prerender在后台加载、渲染目标页,包含子资源、脚本执行和脚本触发的数据请求目标页很可能被访问,并且副作用可控

从表格可以看出, prerender 优化更激进:浏览器会在后台准备一个隐藏页面,目标页的 DOM、样式、布局、脚本都可能已经跑过。用户点击时,浏览器可以直接后台页面激活到前台。如果后台页面还没完全准备好,也能带着已经完成的部分继续加载。

公开案例能说明它的上限。web.dev 上有几个相关数据:Ray-Ban 使用 prerender 后,移动端 LCP 从 4.69 秒降到 2.66 秒;Monrif 的桌面 LCP 改善了 17.9%,用户参与度提升 8.9%。[1] 这些数字不代表每个博客都能复现同样收益,但足够说明它在合适场景下确实有实际价值。

博客站点的规则示例

如果只是替代 instant.page,prefetch 已经够用。本站的情况是:静态博客的站内链接状态简单,可以把一部分普通文章跳转交给 prerender

一个克制的起点可以这样写:

<script type="speculationrules">  {    "prerender": [      {        "where": {          "and": [            { "href_matches": "/*" },            { "not": { "selector_matches": "a[href^='#']" } },            { "not": { "selector_matches": "a[href*='?']" } },            { "not": { "selector_matches": "a[target='_blank']" } },            { "not": { "selector_matches": "a[download]" } },            { "not": { "selector_matches": "a[rel~='nofollow']" } },            { "not": { "selector_matches": "a[data-no-prerender]" } }          ]        },        "eagerness": "moderate"      }    ]  }</script>

如果站点比较复杂,把上面的 "prerender" 改成 "prefetch" ,先用浏览器原生能力替掉 instant.page,再考虑是不是要上 prerender,是更稳妥的迁移顺序。

eagerness 怎么选

eagerness 控制浏览器什么时候开始预测加载。它不是“越积极越好”,而是在命中率和资源浪费之间找一个位置。

大致触发时机我的理解
immediate规则被观察到后尽快开始只适合极少数几乎必点的入口
eagermoderate 更早适合少量高概率链接,不适合全站铺开
moderate桌面端通常是 hover 约 200ms,或更早的 pointerdown普通内容站较均衡的起点
conservative接近实际按下鼠标或触屏时最省资源,收益也最有限

移动端没有 hover,Chrome 会用触摸和视口相关的启发式来判断意图,所以不要把 moderate 机械理解成“鼠标悬停 200ms”。这意味着同一套规则在不同设备上会有不同触发策略,浏览器会根据当前环境压制一部分预测加载。

Chrome 也有硬上限,防止站点过度占用用户设备资源。当前文档里的限制是:

eagernessPrefetchPrerender
immediate5010
eager / moderate / conservative2,FIFO2,FIFO

也就是说,像 moderate 这种依赖用户交互的规则,同一时间通常只保留两个候选;新候选进来,旧候选会被取消。再加上 Save-Data、低电量省电模式、内存压力、用户关闭“预加载网页”、某些扩展主动禁止预加载等情况,最终是否执行仍然由浏览器拍板。

本站需要防的副作用

prerender 的风险要按应用类型看。对本站这种个人博客来说,没有登录、登出、购物车、支付、库存这些状态,所以不需要把所有业务系统的坑都搬进来。

如果是电商、后台、带登录态或交易流程的应用,风险范围会大很多,比如登出 URL、加购、库存状态、个性化推荐、下单接口等。可以参考这篇文章对相关风险的整理:Speculation Rules API zero-ms page navigation

本站首先处理的是统计偏差prerender 会让目标页脚本在后台运行,pageview计数可能在用户真正进入页面前触发。而如果用户最后没有点击这个链接,那么统计值会高于实际值。

const whenActivated = new Promise((resolve) => {  if (document.prerendering) {    // 当前页面还在后台预渲染,等它真正被用户打开后再继续。    document.addEventListener("prerenderingchange", resolve, { once: true });  } else {    // 普通导航进入的页面已经可见,可以直接执行后续初始化。    resolve();  }});whenActivated.then(() => {  // 这些逻辑只应该在页面真正展示后运行,避免预渲染阶段污染统计。  initAnalytics();  initComments();});

第二个需要注意的问题是额外请求压力。如果对大量链接都使用 immediate,服务端的请求量就会随之增加,尤其当其中包含一些高开销的计算或资源请求时,账单也就跟着来了 🧾

这也是本站选择 moderate的原因:等用户表现出访问意图后再预渲染,资源浪费更少。

prerender 和 Swup/PJAX 的冲突

prerender 的前提是 document navigation,也就是浏览器从一个 HTML 文档切换到另一个 HTML 文档。

而 Swup、PJAX 会拦截链接点击,自己拉取 HTML 并更新页面内容。整个过程中,浏览器眼里始终只有同一个 document,并没有发生真正的页面导航。既然如此,提前准备好的 prerender 页面自然也就无从激活。

因此,本站最终弃用了 Swup,重新回归浏览器原生导航,并借助跨文档 View Transitions API 做了一点轻量优化。目前实现的效果比较简单:页面切换时,导航栏不会随着整页重载而闪烁。

不过这都是让Codex生成的,实现原理一概不知,等研究明白之后,再单独写一篇分析。

验证方法

验证方法难以文字阐述,吃力不讨好。写到这也累了,感兴趣可自行观看视频👇

结论

如果目标只是替代 instant.page,先用 Speculation Rules 的 prefetch 就可以了。如果想用 prerender,要考虑前文的副作用,以及和Swup/PJAX等SPA方案的兼容性。


  1. 数据来源:web.dev 的 Ray-Ban 和 Monrif 案例研究,分别见 Ray-Ban case studyMonrif case study;访问日期:2026-06-04。 ↩︎

CompactRelaxed
Normal1.70