Speculation Rules API实战:预取与预渲染让页面秒开

Ray-Ban用Speculation Rules API实现LCP提升43%、移动端转化率翻倍。本文详解预取与预渲染的分层策略、eagerness四级配置、动态注入、副作用处理,以及Chrome 144新增的prerender_until_script,帮你用最少代码实现页面秒开。

为什么你的多页应用需要Speculation Rules API?

聊到页面导航性能,大多数开发者第一反应是优化当前页面——压缩资源、懒加载图片、减少阻塞脚本。这些当然都对,但换个角度想想:如果在用户点击之前,下一个页面就已经准备好了呢?

这就是Speculation Rules API想要解决的问题。简单来说,它把传统的"点击→请求→等待→渲染"流程,变成了"预测→准备→点击→秒开"。

先看看实际数据。Ray-Ban用了Speculation Rules API之后,LCP直接提升43%,移动端转化率翻了一倍多(+101%)。意大利新闻出版商Monrif那边,LCP改善了17.9%,用户参与度涨了8.9%。这些都是2025年的真实生产数据,不是什么实验室跑分。

说实话,我第一次看到Ray-Ban那个转化率数据的时候也是不太敢信。但仔细想想,页面秒开带来的用户体验提升确实是全方位的——用户不等待,就不会流失。

截至2026年2月,这个API已经在所有Chromium内核浏览器(Chrome、Edge、Opera)中全面可用,W3C标准化也在推进中。如果你的网站是多页应用(MPA),那这大概是你今年能做的投入最小、回报最大的性能优化了。

Speculation Rules API基础:预取 vs 预渲染

Speculation Rules API提供两种推测加载方式。理解它们的区别很关键,选错了要么效果不够、要么浪费资源。

预取(Prefetch)——轻量级加速

预取只做一件事:提前下载目标页面的HTML文档。不渲染,不执行JS,就是把HTML先拿到手。

它的核心价值是提前把TTFB的时间成本给消化掉。等用户真正点击时,HTML已经在内存里了,浏览器直接解析渲染就行。

  • 开销很小,可以大规模使用
  • 典型场景:对页面上所有内部链接都做预取
  • 效果:省掉100-500ms的网络往返时间

预渲染(Prerender)——完整的提前加载

预渲染就激进多了。它不光下载HTML,还会在后台的隐藏标签页里把整个页面完整渲染一遍——加载子资源、跑JavaScript、算样式、排布局,全套流程走完。用户点击的时候,浏览器只需要把这个隐藏标签页"翻"到前台就行了。

效果?近乎零延迟的页面切换。

  • 开销大,得谨慎用
  • 典型场景:对用户极可能访问的1-2个页面做预渲染
  • 效果:LCP接近零、加载阶段的CLS消失、用户几乎感知不到等待

两者的选择策略

维度预取(Prefetch)预渲染(Prerender)
资源开销低(仅HTML文档)高(完整页面渲染)
性能收益节省TTFB节省TTFB + FCP + LCP
适用范围广泛预取精准预渲染
Chrome并发限制较宽松最多2-10个(取决于eagerness)
推荐策略所有内部链接最可能访问的1-2个页面

快速上手:5分钟实现页面秒开

好了,进入实战环节。最简单的用法是在HTML里加一个<script type="speculationrules">标签——不需要装npm包,不需要改构建配置,纯JSON声明式搞定。

列表规则:手动指定URL

<script type="speculationrules">
{
  "prerender": [{
    "source": "list",
    "urls": ["/products", "/about"]
  }],
  "prefetch": [{
    "source": "list",
    "urls": ["/blog", "/contact", "/pricing"]
  }]
}
</script>

这段代码做了两件事:立即预渲染/products/about,同时预取/blog/contact/pricing的HTML。就这么简单。

文档规则:自动匹配页面中的链接

手动列URL太累了?文档规则(Document Rules)可以让浏览器自动扫描页面里的链接:

<script type="speculationrules">
{
  "prerender": [{
    "source": "document",
    "where": {
      "and": [
        { "href_matches": "/*" },
        { "not": { "href_matches": "/logout" } },
        { "not": { "href_matches": "/admin/*" } },
        { "not": { "selector_matches": ".no-prerender" } }
      ]
    },
    "eagerness": "moderate"
  }]
}
</script>

意思很直白:匹配所有内部链接,但排除/logout/admin/*路径和带.no-prerender类名的链接。用户鼠标悬停200ms后就触发预渲染。

eagerness策略详解:什么时候该触发推测

eagerness可以说是整个Speculation Rules API里最关键的调优旋钮了。它控制的是"什么时候开始预取或预渲染"这个时机问题。2026年的Chrome对各个级别做了不少更新:

四种eagerness级别

级别触发时机适用场景
immediate规则解析后立即执行确定性极高的目标(如列表规则里的固定URL)
eager桌面端:悬停10ms;移动端:链接进入视口50ms后高置信度的导航预测
moderate桌面端:悬停200ms;移动端:基于视口启发式文档规则的默认值,性价比最高
conservative用户mousedown/touchstart时最保守,几乎零浪费

2026年移动端的重要变化

这个值得单独说一下。从2026年1月起,Chrome在移动端对moderate做了一个比较大的改动——触发逻辑从悬停改成了基于视口的启发式判断:

  • 链接距离上次触摸位置在视口垂直方向30%以内
  • 链接面积至少是视口中最大链接的0.5倍
  • 用户停止滚动500ms后才开始推测(避免滚动时疯狂触发)

坦白讲这个改进很有必要。移动端本来就没有真正的hover事件嘛,之前的悬停检测在手机上一直是个不太靠谱的信号。现在改成根据滚动行为和链接的视觉显著性来判断,逻辑上合理多了。

分层策略:从预取到预渲染的渐进升级

实际项目里,最佳方案不是在预取和预渲染之间二选一,而是分层叠加使用。这个思路最早是CSS Wizardry的Harry Roberts提出来的,我觉得确实很优雅:

  1. 第一层:所有内部链接立即预取(开销几乎为零)
  2. 第二层:用户悬停时,升级为预渲染(有了更强的意图信号)
  3. 第三层:已知的高频路径,直接立即预渲染(确定性很高)
<script type="speculationrules">
{
  "prefetch": [{
    "source": "document",
    "where": { "href_matches": "/*" },
    "eagerness": "immediate"
  }],
  "prerender": [
    {
      "source": "document",
      "where": { "href_matches": "/*" },
      "eagerness": "moderate"
    },
    {
      "source": "list",
      "urls": ["/", "/products"],
      "eagerness": "immediate"
    }
  ]
}
</script>

拆开来看:页面一加载就预取所有内部链接的HTML;用户悬停某个链接时升级为完整预渲染;首页和产品页这种高频入口则直接立即预渲染。

为什么这套分层策略好使?核心原因是预取的成本真的很低——就一个HTTP请求拿HTML而已。就算用户最终没访问那些页面,浪费的带宽也就几十KB。而预渲染只在更强的意图信号出现时才触发,不会白白烧CPU和内存。

JavaScript动态注入:适配SPA和动态内容

静态的JSON声明不是万能的。有些场景需要根据用户行为、页面状态来动态决定预渲染目标,这时候就得用JavaScript来注入规则了:

// 先检查浏览器是否支持
if (HTMLScriptElement.supports &&
    HTMLScriptElement.supports("speculationrules")) {

  const rules = {
    prerender: [{
      source: "list",
      urls: getTopNavigationTargets(),  // 根据分析数据动态生成
      eagerness: "moderate"
    }]
  };

  const script = document.createElement("script");
  script.type = "speculationrules";
  script.textContent = JSON.stringify(rules);
  document.body.append(script);
}

function getTopNavigationTargets() {
  // 可以根据用户画像、当前页面、A/B测试组等动态决定
  const currentPath = location.pathname;
  if (currentPath === "/") {
    return ["/products", "/featured"];
  }
  if (currentPath.startsWith("/products")) {
    return ["/cart", "/checkout"];
  }
  return [];
}

这个模式在电商场景下特别香:首页预渲染产品列表→产品页预渲染购物车→购物车预渲染结算页。整条购物链路都是秒开,用户体验拉满。

在SPA中使用Speculation Rules

虽然Speculation Rules API主要是给MPA设计的,但SPA也不是完全用不上。现代SPA一般都做了路由级别的代码分割(Code Splitting),每个路由对应独立的chunk。你可以用预取来提前加载这些JS chunk:

// SPA中预取路由chunk的示例
if (HTMLScriptElement.supports?.("speculationrules")) {
  // 虽然SPA没法预渲染路由,但预取相关资源还是有用的
  // 配合import()和路由预加载,效果更佳
  const prefetchRoutes = ["/dashboard", "/settings", "/profile"];

  const script = document.createElement("script");
  script.type = "speculationrules";
  script.textContent = JSON.stringify({
    prefetch: [{
      source: "list",
      urls: prefetchRoutes,
      eagerness: "moderate"
    }]
  });
  document.body.append(script);
}

2026年新功能:prerender_until_script

Chrome 144带来了一个挺有意思的新能力(目前在Origin Trial阶段):prerender_until_script。你可以把它理解成预取和预渲染之间的"中间地带"。

工作原理

prerender_until_script的做法是:像完整预渲染一样下载HTML和所有子资源、开始渲染页面,但一碰到<script>标签就暂停(不管是内联还是外部脚本)。等用户真正导航过来之后,才继续执行脚本。

<script type="speculationrules">
{
  "prerender_until_script": [{
    "source": "document",
    "where": { "href_matches": "/*" },
    "eagerness": "moderate"
  }]
}
</script>

为什么这很重要?

完整预渲染有个很头疼的问题:副作用。页面上的Google Analytics、广告追踪这些脚本一跑起来,预渲染阶段就会产生虚假的PV数据。prerender_until_script通过"不执行脚本"这一招直接规避了这个问题,同时CSS解析、布局计算、图片加载这些性能收益全都保留了。

对于博客、新闻、文档这类JS主要集中在页面底部的内容站来说,效果尤其好。页面基本上能完成90%以上的预渲染,就差最后那点脚本执行。

处理副作用:分析统计和服务端影响

副作用处理是上线预渲染之前必须搞定的事情。下面这些方案都是经过生产验证的。

在服务端识别预取请求

预取请求会带一个特殊的HTTP头Sec-Purpose: prefetch,服务端可以根据这个来做区分处理:

# Nginx配置示例
location / {
    # 检测预取请求,跳过某些服务端逻辑
    if ($http_sec_purpose = "prefetch") {
        # 跳过访问计数、日志记录等
        set $skip_analytics 1;
    }
}

在客户端延迟分析代码

prerenderingchange事件可以检测页面什么时候从预渲染状态切换到了活跃状态:

// 等预渲染页面被激活后再触发分析统计
function trackPageView() {
  analytics.track("page_view", {
    url: location.href,
    timestamp: Date.now()
  });
}

if (document.prerendering) {
  // 当前正在预渲染,等激活了再说
  document.addEventListener("prerenderingchange", () => {
    trackPageView();
  }, { once: true });
} else {
  // 正常加载,直接统计
  trackPageView();
}

这段代码是通用方案,几乎适用于所有分析工具。如果你用的SDK还没有内置预渲染支持,直接拿去用就行。

No-Vary-Search:搜索参数智能匹配

做电商的朋友应该对这个场景很熟悉:你预渲染了/products页面,但用户实际点击的是/products?color=red。默认情况下浏览器会认为这是两个不同的页面,你的预渲染就白做了。

挺亏的对吧?No-Vary-Search就是来解决这个问题的:

<script type="speculationrules">
{
  "prefetch": [{
    "source": "list",
    "urls": ["/products"],
    "expects_no_vary_search": "params=(\"color\" \"size\" \"sort\")"
  }]
}
</script>

服务端同步返回对应的响应头:

No-Vary-Search: params=("color" "size" "sort")

这等于告诉浏览器:colorsizesort这几个查询参数不影响页面主体内容,预取的缓存可以复用。不过要注意,如果页面有客户端渲染的内容,激活之后还是需要根据实际URL参数来更新视图。

用Chrome DevTools调试Speculation Rules

"我的预渲染到底生效没?"——别猜了,Chrome DevTools有专门的面板帮你看。

调试步骤

  1. 打开Chrome DevTools(F12)
  2. 切到Application面板
  3. 在左侧Background services下找到Speculative loads
  4. 这里有三个子面板:
    • Speculation Rules——当前页面解析出的所有规则
    • Speculations——每个预取/预渲染URL的状态
    • This Page——当前页面本身是不是通过预渲染加载的

有个坑要注意:如果你是先加载了页面再打开DevTools,那推测数据会看不到。因为DevTools是通过CDP(Chrome DevTools Protocol)实时监控的,没开的时候就不会记录。解决办法很简单——刷新一下页面就好了。

在控制台快速验证

// 检查当前页面是否通过预渲染加载
const navEntry = performance.getEntriesByType("navigation")[0];

console.log("导航类型:", navEntry.type);
// 如果是 "prerender",说明命中了预渲染

console.log("激活时间:", navEntry.activationStart);
// 大于0表示经过了预渲染阶段

Network面板的小细节

预取请求在Network面板里能正常看到,优先级显示为Lowest(毕竟当前页面的资源更重要)。点进去能看到Sec-Purpose: prefetch这个头。但是——预渲染的请求在Network面板里是看不到的,因为它们跑在独立的渲染进程里。要看预渲染状态,只能去Application面板的Speculative loads。

真实案例:数据不说谎

到底Speculation Rules API在生产环境效果如何?让数据来说话。

Ray-Ban电商平台(2025)

指标改善幅度
LCP(桌面+移动)提升43%
移动端转化率+101.47%
桌面端转化率+156.16%
移动端每次会话页面浏览+51.95%
桌面端每次会话页面浏览+65.30%

这组数据确实夸张。桌面端转化率涨了156%,我猜很大一部分原因是Ray-Ban本身图片特别多、页面比较重,预渲染带来的体验跳跃感更强烈。

Monrif新闻网站(2025)

指标改善幅度
LCP提升17.9%
用户参与度+8.9%

Akamai/Scalemates(2024-2025)

指标改善幅度
P95 LCP快约500ms
P75 LCP快约170ms
预渲染命中率59%的导航

这里有个细节需要提一下:这些改善在整站的CrUX数据里可能看起来不那么明显。原因是CrUX报告的是P75数据,而很多导航(比如用户第一次通过搜索引擎进来的着陆页)本身就不受推测规则影响。想准确衡量效果的话,建议用RUM工具把推测导航和普通导航分开对比。

注意事项与最佳实践清单

Chrome的资源保护机制

放心,Chrome不会让预渲染把用户设备搞崩。它有好几层保护:

  • 内存中最多保留2个预渲染页面(先进先出,新的把旧的挤掉)
  • 设备开了省电模式且电量低?自动关闭推测
  • 内存不够了?停止推测
  • 用户关了"预加载页面"设置也不会触发(注意uBlock Origin等扩展会默认关闭这个)

安全边界

  • 预渲染仅限同站页面(a.example.com可以预渲染b.example.com,但不能跨站)
  • 跨域预渲染需要目标页面设置Supports-Loading-Mode: credentialed-prerender响应头
  • 跨站预取不携带Cookies(隐私保护)
  • 千万不要对有服务端副作用的URL(如/api/consume-credit这种扣费接口)开预取或预渲染

实施建议清单

  1. 渐进式推出:先预取 → 再moderate预渲染 → 最后考虑升级到eager
  2. 排除危险路径:登出、后台管理、支付、API调用这些一定要排除
  3. 监控服务器压力:预取会增加请求量,确认基础设施扛得住
  4. 处理分析统计:用prerenderingchange事件延迟触发
  5. 定期检查DevTools:确认规则正确解析、推测正常执行
  6. 搭配bfcache:预渲染管前进导航,bfcache管后退导航,双管齐下效果最好

常见问题(FAQ)

Speculation Rules API和旧版link rel="prerender"有什么区别?

完全不同的东西。旧的<link rel="prerender">在Chrome后期实际上只做NoState Prefetch(不完整的预渲染),而且语法很局限。Speculation Rules API是全新设计的API,提供了列表规则、文档规则、四级eagerness控制、FIFO内存管理,以及更完善的副作用处理方案。不是简单的升级迭代,是从头来过。

Safari和Firefox支持吗?

截至2026年2月,还没有。目前只有Chromium系浏览器支持(Chrome、Edge、Opera)。不过好消息是W3C标准化在推进中,其他浏览器厂商也已经表达了兴趣。关键是——在不支持的浏览器里,<script type="speculationrules">会被直接忽略,不报错、没副作用。所以放心大胆地当渐进增强用就好了。

预渲染会不会浪费用户流量?

有这个可能,所以策略很重要。Chrome自身有保护(省电模式关闭、内存不足停止、最多2个预渲染),但开发者这边也要做好控制。最佳实践就是我们前面讲的分层策略:大面积预取(开销小),精准预渲染(开销大但命中率高)。预取HTML通常也就几十KB,浪费了也不心疼。

怎么衡量Speculation Rules的效果?

几个途径:客户端用performance.getEntriesByType('navigation')[0].type看导航类型是不是prerender;DevTools的Application面板看推测状态;RUM工具里按导航类型分段对比LCP;CrUX数据里观察navigate_cacheprerender类型占比变化。我的建议是同时跟踪预渲染命中率和业务指标(转化率、跳出率),这样才能看出真正的ROI。

能和Service Worker一起用吗?

能。预取请求会正常经过Service Worker,缓存策略和离线逻辑都不受影响。预渲染页面在隐藏标签页里运行,行为跟普通页面基本一样,Service Worker该注册注册、该控制控制。但有一点要留意:如果你的Service Worker拦截并修改了预取响应,确保这个修改逻辑跟推测规则的预期是一致的,别搞出冲突来。

关于作者 Editorial Team

Our team of expert writers and editors.