推测规则 API

实验性: 这是一项实验性技术
在生产中使用此技术之前,请仔细检查浏览器兼容性表格

推测规则 API 旨在提高未来导航的性能。它针对文档 URL 而不是特定的资源文件,因此适用于多页面应用程序(MPA)而不是单页面应用程序(SPA)。

推测规则 API 提供了广泛可用的 <link rel="prefetch"> 功能的替代方案,旨在取代仅限 Chrome 且已弃用的 <link rel="prerender"> 功能。它在这些技术上提供了许多改进,同时提供了更具表现力、可配置的语法,用于指定哪些文档应被预取或预渲染。

注意: 推测规则 API 不处理子资源预取;为此你需要使用 <link rel="prefetch">

概念与用法

推测规则可以在内联 <script type="speculationrules"> 元素和由 Speculation-Rules 响应头引用的外部文本文件中指定。规则以 JSON 结构指定。

脚本示例

html
<script type="speculationrules">
  {
    "prerender": [
      {
        "where": {
          "and": [
            { "href_matches": "/*" },
            { "not": { "href_matches": "/logout" } },
            { "not": { "href_matches": "/*\\?*(^|&)add-to-cart=*" } },
            { "not": { "selector_matches": ".no-prerender" } },
            { "not": { "selector_matches": "[rel~=nofollow]" } }
          ]
        }
      }
    ],
    "prefetch": [
      {
        "urls": ["next.html", "next2.html"],
        "requires": ["anonymous-client-ip-when-cross-origin"],
        "referrer_policy": "no-referrer"
      }
    ]
  }
</script>

如果网站包含 Content-Security-Policyscript-src 指令,则需要明确允许使用 <script> 元素进行的推测规则。这可以通过添加 'inline-speculation-rules' 源、哈希源或 nonce 源之一来完成。

HTTP 头示例

http
Speculation-Rules: "/rules/prefetch.json"

包含推测规则 JSON 的文本资源可以有任何有效的名称和扩展名,但它必须以 application/speculationrules+json MIME 类型提供。

注意: 规则可以同时使用内联脚本和 HTTP 头指定 — 应用于文档的所有规则都会被解析并添加到文档的推测规则列表中。

你为每种推测性加载类型(例如 "prerender""prefetch")指定一个不同的数组来包含规则。每个规则都包含在一个对象中,该对象例如指定要获取的资源列表,以及每个规则的选项,例如明确的 Referrer-Policy 设置。请注意,预渲染的 URL 也会被预取。

有关可用语法的完整说明,请参阅 <script type="speculationrules">

使用预取

<script type="speculationrules"> 元素或 Speculation-Rules 头中包含 prefetch 规则将使支持的浏览器下载引用的页面的响应正文,但不会下载页面引用的任何子资源。当导航到预取页面时,它将比未预取时渲染得更快。

结果保存在每个文档的内存缓存中。当你离开当前页面时,所有缓存的预取都将被丢弃,当然除了你随后导航到的预取文档。

这意味着如果你预取了用户没有导航到的内容,通常会浪费资源,尽管如果头部允许,结果可能会填充 HTTP 缓存。话虽如此,预取的前期成本远小于预渲染的前期成本,因此我们鼓励你广泛采用预取,例如预取你网站上的所有重要页面,前提是它们可以安全预取(有关更多详细信息,请参阅不安全的推测性加载条件)。

同站和跨站预取都将起作用,但跨站预取受到限制(有关两者之间差异的解释,请参阅“同站”和“跨站”)。出于隐私原因,跨站预取目前仅在用户未为目标站点设置 cookie 的情况下才有效 — 我们不希望站点能够通过预取页面(他们甚至可能从未实际访问过)来跟踪用户活动,基于先前设置的 cookie。

注意: 将来将通过 Supports-Loading-Mode 头提供跨站预取的选择加入,但在撰写本文时尚未实现(仅提供了跨域、同站预渲染的选择加入)。

对于支持它的浏览器,应优先使用推测规则预取,而不是较旧的预取机制,即 <link rel="prefetch">fetch(),其中设置了 priority: "low" 选项。因为我们知道推测规则预取是用于导航的,而不是一般的资源预取。

  • 它可用于跨站导航,而 <link rel="prefetch"> 不能。
  • 它不会被 Cache-Control 头阻止,而 <link rel="prefetch"> 经常会被阻止。

此外,推测规则预取

  • 在需要时自动降低优先级(fetch() 不会)。
  • 尊重用户的配置。例如,当用户的设备处于省电模式或数据节省模式时,不会发生预取。
  • 将预取资源存储在每个文档的内存缓存中,而不是 HTTP 缓存中,这可能会导致稍微更快的预取。

使用预渲染

<script type="speculationrules"> 元素或 Speculation-Rules 头中包含 prerender 规则将使支持的浏览器获取、渲染并加载内容到一个不可见的标签页中,存储在每个文档的内存缓存中。这包括加载所有子资源,运行所有 JavaScript,甚至加载子资源和执行由 JavaScript 启动的数据获取。当你离开当前页面时,所有缓存的预渲染及其子资源都将被丢弃,当然除了你随后导航到的预渲染文档。

未来导航到预渲染页面将几乎是即时的。浏览器会激活不可见的标签页,而不是执行通常的导航过程,用预渲染页面替换旧的前景页面。如果页面在完全预渲染之前被激活,它将以其当前状态激活,然后继续加载,这意味着你仍然会看到显著的性能改进。

预渲染会消耗内存和网络带宽。如果你预渲染了用户没有导航到的内容,这些资源就会被浪费(尽管如果头部允许,结果可能会填充 HTTP 缓存,从而允许以后使用)。预渲染的前期成本远大于预取的前期成本,其他条件也可能导致内容不安全而无法预渲染(有关更多详细信息,请参阅不安全的推测性加载条件)。因此,我们鼓励你更谨慎地采用预渲染,仔细考虑页面被导航到的可能性很高的情况,并且你认为用户体验的收益值得额外的成本。

注意: 为了说明潜在资源浪费的程度,预渲染使用的资源量与渲染一个 <iframe> 大致相同。

注意: 许多 API 在预渲染期间/直到激活之前将自动推迟。有关更多详细信息,请参阅预渲染期间推迟或受限的平台功能

预渲染默认仅限于同源文档。跨域、同站预渲染是可能的 — 它需要导航目标使用 Supports-Loading-Mode 头并设置为 credentialed-prerender 值来选择加入。目前无法进行跨站预渲染。

对于支持它的浏览器,应优先使用推测规则预渲染,而不是较旧的预渲染机制,即 <link rel="prerender">

  • <link rel="prerender"> 是 Chrome 特有的,从未标准化,Chrome 工程团队正在逐步淘汰它。
  • 它加载通过 JavaScript 加载的子资源,而 <link rel="prerender"> 不会。
  • 它不会被 Cache-Control 设置阻止,而 <link rel="prerender"> 经常会被阻止。
  • 推测规则预渲染应被视为一种提示和渐进增强。与 <link rel="prerender"> 不同,它是一种推测性提示,浏览器可能会根据用户设置、当前内存使用情况或其他启发式方法选择不执行该提示。

推测规则 API 功能检测

你可以使用以下代码检查是否支持推测规则 API:

js
if (
  HTMLScriptElement.supports &&
  HTMLScriptElement.supports("speculationrules")
) {
  console.log("Your browser supports the Speculation Rules API.");
}

例如,你可能希望在支持的浏览器中插入用于预取的推测规则,但在其他浏览器中使用较旧的技术,例如 <link rel="prefetch">

js
if (
  HTMLScriptElement.supports &&
  HTMLScriptElement.supports("speculationrules")
) {
  const specScript = document.createElement("script");
  specScript.type = "speculationrules";
  const specRules = {
    prefetch: [
      {
        source: "list",
        urls: ["/next.html"],
      },
    ],
  };
  specScript.textContent = JSON.stringify(specRules);
  document.body.append(specScript);
} else {
  const linkElem = document.createElement("link");
  linkElem.rel = "prefetch";
  linkElem.href = "/next.html";
  document.head.append(linkElem);
}

检测预取和预渲染的页面

本节介绍检测请求页面是否已预取或预渲染的不同方法。

服务器端检测

预取和预渲染的页面请求会发送 Sec-Purpose 请求头:

用于预取

http
Sec-Purpose: prefetch

用于预渲染

http
Sec-Purpose: prefetch;prerender

服务器可以根据此头进行响应,例如,记录推测性加载请求,返回不同的内容,甚至阻止推测性加载发生。如果返回非成功响应代码(重定向后 HTTP 状态码不在 200-299 范围内),则页面将不会被预取/预渲染。此外,204 和 205 状态码也会阻止预渲染(但不会阻止预取)。

使用非成功代码(例如 503)是阻止服务器端推测性加载的最简单方法,尽管通常更好的方法是允许预取/预渲染,并使用 JavaScript 延迟任何应仅在实际查看页面时才发生的动作。

JavaScript 预取检测

当页面被预取时,其 PerformanceResourceTiming.deliveryType 条目将返回 "navigational-prefetch" 值。你可以使用以下代码在收到类型为 "navigational-prefetch" 的性能条目时运行函数:

js
if (
  performance.getEntriesByType("navigation")[0].deliveryType ===
  "navigational-prefetch"
) {
  respondToPrefetch(); // Author-defined function
}

当测量性能时,或者当你想推迟可能在预取期间导致问题的操作时(参见不安全的预取),此技术很有用。

JavaScript 预渲染检测

要在页面预渲染时运行活动,你可以检查 Document.prerendering 属性。例如,你可以运行一些分析:

js
if (document.prerendering) {
  analytics.sendInfo("got this far during prerendering!");
}

当预渲染文档被激活时,PerformanceNavigationTiming.activationStart 被设置为一个 DOMHighResTimeStamp,表示预渲染开始到文档激活之间的时间。以下函数可以检查预渲染预渲染页面:

js
function pagePrerendered() {
  return (
    document.prerendering ||
    self.performance?.getEntriesByType?.("navigation")[0]?.activationStart > 0
  );
}

当用户查看页面激活预渲染页面时,将触发 prerenderingchange 事件。这可以用于启用那些原本在页面加载时默认启动,但你希望延迟到用户查看页面时才启动的活动。以下代码设置了一个事件监听器,用于在预渲染完成时(在预渲染页面上)运行一个函数,或者在非预渲染页面上立即运行它:

js
if (document.prerendering) {
  document.addEventListener("prerenderingchange", initAnalytics, {
    once: true,
  });
} else {
  initAnalytics();
}

不安全的推测性加载条件

本节涵盖了需要注意的条件,在这些条件下预取和/或预渲染是不安全的。这意味着预取/预渲染呈现这些条件的页面可能需要在你的代码中进行缓解,或者需要完全避免。

不安全的预取

如前所述,我们建议广泛采用预取,因为风险与收益之比相当低——资源浪费的潜力很小,而性能提升可能很大。但是,你需要确保预取页面不会对应用程序的流程造成问题。

当执行预取时,浏览器通过单个 GET 请求下载引用页面的响应正文,用户可能在未来某个时间导航到该页面。问题可能特别出现在请求的 URL 执行服务器端副作用时,而你希望这些副作用直到导航到该 URL 时才发生。

例如

  • 退出 URL。
  • 语言切换 URL。
  • “添加到购物车”URL。
  • 登录流程 URL,其中服务器导致发送短信,例如作为一次性密码(OTP)。
  • 增加用户使用额度的 URL,例如消耗其每月免费文章额度或启动其每月分钟计时器。
  • 启动服务器端广告转化跟踪的 URL。

此类问题可以在服务器端通过监听传入请求中的 Sec-Purpose: prefetch 头来缓解,然后运行特定代码来延迟有问题的功能。稍后,当实际导航到页面时,如果需要,你可以通过 JavaScript 启动延迟的功能。

注意: 有关检测代码的更多详细信息,请参阅检测预取和预渲染页面部分。

预取一个服务器渲染内容会因用户在当前页面上采取的操作而改变的文档也存在潜在风险。这可能包括例如限时抢购页面或电影院座位图。仔细测试此类情况,并通过在页面加载后更新内容来缓解此类问题。有关这些情况的更多详细信息,请参阅服务器渲染的可变状态

注意: 浏览器会将预取页面缓存一小段时间(例如 Chrome 缓存 5 分钟),然后丢弃它们,因此在任何情况下,你的用户都可能会看到最多 5 分钟过时的内容。

可以使用 Clear-Site-Data 响应头的 prefetchCache 值清除过期的预取。例如,当状态更改请求导致缓存数据不再有效时(例如退出网站时),可以使用此方法。

如果获取页面的所有副作用都来自 JavaScript 执行,那么预取是安全的,因为 JavaScript 在激活之前不会运行。

最后一个提示是审核你 robots.txt 文件中列为不允许的 URL —— 通常这些 URL 指向只有经过身份验证的用户才能访问的页面,因此不应包含在搜索引擎结果中。其中许多 URL 都没有问题,但这可能是一个寻找不安全预取 URL(即,它们表现出上述条件)的好地方。

不安全的预渲染

预渲染比预取更具风险,因此应谨慎使用,仅在值得的情况下进行。预渲染需要注意更多不安全的条件,因此,尽管回报更高,风险也更大。

当完成预渲染时,浏览器会获取 URL 并将内容渲染并加载到不可见的选项卡中。这包括运行内容的 JavaScript 并加载所有子资源,包括通过 JavaScript 获取的资源。如果观察到以下任何条件,则内容可能不安全而无法预渲染:

  • 该 URL 不安全以进行预取。如果你尚未阅读,请先阅读上一节,并了解这些条件同样适用于不安全的预渲染。
  • 页面加载时,其 JavaScript 以可能导致其他未预渲染页面(用户当前正在查看的页面)中出现混淆效果的方式修改客户端存储(例如 Web 存储IndexedDB)。
  • 页面运行 JavaScript 或加载图像,导致副作用,例如发送分析、记录广告印象,或以用户已经与应用程序交互的方式修改应用程序的状态。同样,这可能会影响应用程序的流程,或导致不正确的性能或使用报告。有关此类用例的更多详细信息,请参阅服务器渲染的可变状态

为了缓解此类问题,你可以使用以下技术:

  • 在服务器端,当请求传入时,监听 Sec-Purpose: prefetch 头,然后运行特定代码以推迟有问题的功能。
  • 使用 prerenderingchange 事件来检测预渲染页面何时实际被激活并运行代码。这在两种情况下很有用:
    • 推迟可能在页面被查看之前运行会导致问题的代码。例如,你可能希望等到激活后才更新客户端存储或使用 JavaScript 修改服务器端状态。这可以避免 UI 和应用程序状态相互不同步的情况,例如购物车显示没有商品,即使用户已经添加了一些商品。
    • 如果上述方法不可行,那么你仍然可以在页面激活后重新运行代码以使应用程序再次保持最新。例如,一个高度动态的限时抢购页面可能依赖于来自第三方库的内容更新。如果你无法延迟更新,你总可以在用户查看页面后获取最新更新。预渲染页面可以使用 Broadcast Channel API 或其他机制(如 fetch()WebSocket)进行实时更新。这保证了用户在预渲染激活后将看到最新的内容。
  • 仔细管理你的第三方分析脚本 — 如果可能,请使用具有预渲染感知功能的脚本(例如,使用 Document.prerendering 属性来延迟在预渲染页面上运行),例如 Google Analytics 或 NewRelic。
    • 请注意,跨域 <iframe> 的内容加载在预渲染期间会被延迟,直到激活发生。这样做是为了避免加载未感知预渲染的跨域页面导致的故障,并避免围绕向这些框架公开哪些凭据和存储的复杂性。这意味着用户在某些情况下最初可能会看到空白框架,但这也意味着大多数第三方小部件(例如广告技术)在预渲染期间可以安全使用。
    • 对于不具备预渲染感知功能的第三方脚本,如前所述,避免在激活之前加载它们,使用 prerenderingchange 事件。

服务器渲染的可变状态

需要关注的服务器渲染状态主要有两种:过时状态用户特定状态。这可能导致不安全的预取和预渲染。

  • 过时状态:考虑一个服务器渲染的博客评论列表的例子,它可能在博客文章被预渲染和被查看之间变得过时。如果当前页面是一个管理员面板,用户正在删除垃圾评论,这可能尤其成问题。如果用户随后导航到博客文章,他们可能会对为什么能看到刚刚删除的垃圾评论感到困惑。
  • 用户特定状态:考虑通过 cookie 跟踪登录状态的示例。可能会出现以下问题:
    • 用户在选项卡 1 中访问 https://site.example/a,在选项卡 2 中访问 https://site.example/b,此时处于注销状态。
    • https://site.example/b 预渲染 https://site.example/c。它将以注销状态预渲染。
    • 用户在选项卡 1 中登录 https://site.example
    • 用户切换到选项卡 2 并点击链接到 https://site.example/c,这将激活预渲染页面。
    • 选项卡 2 显示 https://site.example/c 的注销视图,这让用户感到困惑,因为他们以为自己已登录。

用户特定状态问题也可能发生在其他用户设置上,例如语言设置、深色模式偏好或向购物车添加商品。它们也可能发生在只涉及一个选项卡时:

  • 假设用户访问 https://site.example/product
  • https://site.example.com/product 预渲染 https://site.example.com/cart。它预渲染时购物车中有 0 件商品。
  • 用户点击“添加到购物车”按钮,这会发起一个获取请求,将商品添加到用户的购物车(不重新加载页面)。
  • 用户点击链接到 https://site.example.com/cart,这将激活预渲染页面。
  • 用户看到一个空的购物车,尽管他们刚刚添加了一些东西。

对于这些情况,以及任何内容可能与服务器不同步的情况,最好的缓解措施是让页面根据需要自行刷新。例如,服务器可以使用 Broadcast Channel API 或其他机制,如 fetch()WebSocket。页面可以随后适当地更新自己,包括尚未激活的推测性加载页面。

如果无法刷新,可以使用 Clear-Site-Data 响应头及其 prefetchCacheprerenderCache 值(或两者),适当地清除推测。

该头可以在任何同站 HTTP 请求(例如 /api/add-to-cart API 调用)中返回。

预渲染文档的会话历史行为

从最终用户的角度来看,激活预渲染/已预渲染的文档与任何常规导航行为相似。激活的文档会显示在标签页中并附加到会话历史记录,任何现有的前进历史记录条目都会被修剪。在激活之前在预渲染浏览上下文中发生的任何导航都不会影响会话历史记录。

从开发人员的角度来看,预渲染文档可以被视为具有微不足道的会话历史记录,其中只存在一个条目——当前条目。预渲染上下文中的所有导航实际上都被替换了。

虽然操作会话历史记录的 API 功能(例如 HistoryNavigation)可以在预渲染文档中调用,但它们只操作上下文的微不足道的会话历史记录。因此,预渲染文档不参与其引用页面的联合会话历史记录。例如,它们不能通过 History.back() 导航其引用者。

这种设计确保用户在使用后退按钮时获得预期的体验——即,他们会回到上次看到的内容。一旦预渲染文档被激活,只有单个会话历史记录条目被附加到联合会话历史记录中,忽略在预渲染浏览上下文内发生的任何先前导航。在联合会话历史记录中后退一步——例如,通过按下后退按钮——会将用户带回到引用页面。

预渲染期间推迟或受限的平台功能

由于预渲染页面是在隐藏状态下打开的,因此一些可能导致侵入性行为的 API 功能在此状态下不会被激活,而是被推迟到页面激活之后。其他在预渲染时存在问题的 Web 平台功能则完全受到限制。本节提供了哪些功能被推迟或受到限制的详细信息。

注意: 在少数无法推迟和限制的情况下,预渲染会被取消。

异步 API 延迟

延迟意味着 API 功能会立即返回一个待处理的 Promise,然后什么都不做,直到页面激活。激活后,该功能正常运行,Promise 也正常解决或拒绝。

以下异步功能的执行结果在预渲染文档中会被延迟,直到它们被激活:

隐式受限的 API

以下功能在未激活的文档中将自动失败或不执行任何操作。

需要临时激活粘性激活的 API。

需要包含文档获得焦点的 API。

需要包含文档的 Document.visibilityState"visible" 的 API。

其他受限功能

  • 下载链接,即带有 download 属性的 <a><area> 元素,它们的下载将延迟到预渲染完成后。
  • 无跨站导航:任何导航到不同站点的预渲染文档将在请求发送到该站点之前立即被丢弃。
  • 受限 URL:预渲染文档不能托管非 HTTP(S) 顶级 URL。包含以下 URL 类型将导致预渲染立即被丢弃:
  • 会话存储:可以使用 Window.sessionStorage,但其行为非常特殊,以避免破坏那些期望只有一个页面同时访问标签页会话存储的网站。因此,预渲染页面在创建时会从标签页的会话存储状态克隆一份副本。激活后,预渲染页面的存储副本将被丢弃,并改用标签页的主存储状态。使用会话存储的页面可以使用 prerenderingchange 事件来检测存储交换何时发生。
  • Window.print():任何对此方法的调用都将被忽略。
  • “简单对话框方法”受到以下限制:
  • 专用/共享 worker 脚本已加载,但其执行将延迟到预渲染文档被激活。
  • 跨域 <iframe> 加载在预渲染期间会延迟,直到页面激活后。

接口

推测规则 API 不定义自己的任何接口。

其他接口的扩展

Document.prerendering 实验性

一个布尔属性,如果文档当前正在预渲染过程中,则返回 true

prerenderingchange 事件 实验性

当预渲染文档被激活(即用户查看页面)时,在该文档上触发。

PerformanceNavigationTiming.activationStart 实验性

一个数字,表示文档开始预渲染到激活之间的时间。

PerformanceResourceTiming.deliveryType "navigational-prefetch" 实验性

表示性能条目的类型是预取。

HTTP 标头

Content-Security-Policy 'inline-speculation-rules' 实验性

用于选择允许使用 <script type="speculationrules"> 在被获取的文档上定义推测规则。

Clear-Site-Data 'prefetchCache''prerenderCache' 实验性

用于清除推测。例如,当状态更改导致推测过时时。

Speculation-Rules 实验性

提供一个指向包含推测规则 JSON 定义的文本资源 URL 列表。当响应是 HTML 文档时,这些规则将添加到文档的推测规则集中。

Supports-Loading-Mode 实验性

由导航目标设置,用于选择使用各种高风险加载模式。例如,跨域、同站预渲染需要 Supports-Loading-Mode 值为 credentialed-prerender

HTML 功能

<script type="speculationrules"> 实验性

用于在当前文档内部定义一组预取和/或预渲染推测规则,这些规则将添加到文档的推测规则集中。

示例

有关代码示例,请参阅 developer.chrome.com 上的在 Chrome 中预渲染页面以实现即时页面导航 (2025)

规范

规范
HTML
# 推测性加载
预渲染改版

浏览器兼容性

html.elements.script.type.speculationrules

http.headers.Speculation-Rules

另见

  • 有关推测规则和其他类似性能改进功能的比较,请参阅推测性加载