推测规则 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>

如果站点包含使用 <script> 元素的推测规则,则需要在Content-Security-Policyscript-src指令中显式允许这些规则。这可以通过添加 '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"> 和带有 priority: "low" 选项设置的fetch()。因为我们知道推测规则预取用于导航,而不是一般的资源预取

  • 它可以用于跨站导航,而 <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,服务器会在其中导致发送 SMS,例如作为一次性密码 (OTP)。
  • 会增加用户使用配额的 URL,例如消耗他们的每月免费文章配额或启动他们每月分钟数的计时器。
  • 会启动服务器端广告转化跟踪的 URL。

可以通过在服务器上监视 Sec-Purpose: prefetch 头部来缓解这些问题,并在请求到来时运行特定的代码来延迟有问题的功能。稍后,当页面实际被导航到时,您可以在需要时通过 JavaScript 启动延迟的功能。

注意:您可以在 检测预取和预渲染页面 部分找到有关检测代码的更多详细信息。

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

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

如果获取页面的所有副作用都是由 JavaScript 执行引起的,那么预取是安全的,因为 JavaScript 只有在激活后才会运行。

最后一个提示是审计在您的 robots.txt 文件中列出的被禁止的 URL——通常这些 URL 指向只能被经过身份验证的用户访问的页面,因此不应该包含在搜索引擎结果中。其中很多都是可以的,但它可以是一个很好的地方来查找不适合预取的 URL(即它们表现出上面描述的条件)。

不安全的预渲染

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

当进行预渲染时,浏览器会 GET URL 并将内容渲染并加载到一个不可见的选项卡中。这包括运行内容的 JavaScript 并加载所有子资源,包括由 JavaScript 获取的那些。如果观察到以下任何条件,则内容可能不安全进行预渲染。

  • URL 不安全进行预取。如果您还没有阅读上一节,请先阅读,并了解这些条件也同样适用于不安全的预渲染。
  • 页面的 JavaScript 在加载时修改了客户端存储(例如 Web 存储IndexedDB),这可能会对用户当前正在查看的其他非预渲染页面产生令人困惑的影响。
  • 页面运行 JavaScript 或加载会导致副作用的图像,例如发送分析、记录广告展示或以用户已经与之交互的方式修改应用程序的状态。同样,这会影响应用程序的流程,或者导致不正确的性能或使用情况报告。有关这些用例的更多详细信息,请参见 服务器渲染的动态状态

要缓解这些问题,您可以使用以下技术

  • 在服务器上监视 Sec-Purpose: prefetch 头部,并在请求到来时运行特定的代码来延迟有问题的功能。
  • 使用 prerenderingchange 事件检测预渲染的页面何时被实际激活并运行代码。这在两种情况下很有用
    • 推迟在页面被查看之前运行可能会导致问题的代码。例如,您可能希望等到激活后才更新客户端存储或使用 JavaScript 修改服务器端状态。这可以避免 UI 和应用程序状态彼此不同步的情况,例如,即使用户添加了一些商品,购物车也显示没有商品。
    • 如果上述方法不可行,那么您仍然可以在页面激活后重新运行代码,以使应用程序再次更新。例如,一个高度动态的闪购页面可能依赖于来自第三方库的内容更新。如果您无法延迟更新,那么您始终可以在用户查看页面后获取最新的更新。可以使用 广播通道 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。它会以购物车中没有商品的状态被预渲染。
  • 用户点击"添加到购物车"按钮,这会启动一个获取请求,将商品添加到用户的购物车(不重新加载页面)。
  • 用户点击链接到 https://site.example.com/cart,这会激活预渲染的页面。
  • 用户看到一个空购物车,即使他们刚刚添加了一些商品。

对于这些情况,以及任何内容可能与服务器不同步的时候,最好的缓解方法是让页面根据需要刷新自己。例如,服务器可以使用 广播通道 API 或其他机制(例如 fetch()WebSocket)。页面可以根据需要进行相应的更新,包括那些尚未被激活的推测性加载的页面。

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

从最终用户角度来看,激活一个预渲染/预渲染的文档的行为与任何传统的导航一样。激活的文档显示在选项卡中并附加到会话历史记录,任何现有的前进历史记录条目都会被修剪。在激活之前发生的预渲染浏览上下文中进行的任何导航都不会影响会话历史记录。

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

虽然在预渲染文档中可以调用基于会话历史记录的 API 特性(例如 HistoryNavigation),但它们只作用于上下文的简单会话历史记录。因此,预渲染文档不会参与其引用页面的联合会话历史记录。例如,它们无法通过 History.back() 导航其引用页面。

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

在预渲染期间延迟或限制的平台功能

由于预渲染页面在隐藏状态下打开,因此在该状态下不会激活几个可能导致侵入行为的 API 特性,而是等到页面激活后才 **延迟** 执行。在预渲染时有问题的其他 Web 平台特性则完全被限制。本节详细介绍了哪些特性被延迟或限制。

注意: 在极少数情况下,如果延迟和限制不可行,则会取消预渲染。

异步 API 延迟

延迟意味着 API 特性立即返回一个挂起的 Promise,然后在页面激活之前什么都不做。激活后,特性按正常方式运行,Promise 按正常方式被解决或拒绝。

以下异步特性的结果在预渲染文档中被延迟,直到它们被激活

隐式限制的 API

其他限制的特性

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

接口

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

对其他接口的扩展

Document.prerendering 实验性

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

prerenderingchange 事件 实验性

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

PerformanceNavigationTiming.activationStart 实验性

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

PerformanceResourceTiming.deliveryType "navigational-prefetch" 实验性

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

HTTP 头部

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

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

Speculation-Rules 实验性

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

Supports-Loading-Mode 实验性

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

HTML 功能

<script type="speculationrules"> 实验性

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

示例

规范

规范
推测规则
预渲染改造

浏览器兼容性

api.Document.prerendering

BCD 表格仅在浏览器中加载

api.Document.prerenderingchange_event

BCD 表格仅在浏览器中加载

html.elements.script.type.speculationrules

BCD 表格仅在浏览器中加载

另请参阅