借助“早期提示”利用服务器思考时间来加快页面加载速度

了解服务器如何向浏览器发送有关关键子资源的提示。

什么是早期提示?

随着时间的推移,网站变得越来越复杂。因此,服务器需要执行一些繁琐的工作(例如访问数据库或 CDN 访问源服务器)来为请求的网页生成 HTML,这并不奇怪。遗憾的是,这种“服务器思考时间”会导致浏览器在开始呈现网页之前出现额外的延迟时间。事实上,在服务器准备响应期间,连接实际上处于空闲状态。

一张图片,显示了网页加载和其他资源加载之间的服务器思考时间差为 200 毫秒。
不使用提前提示:服务器上的所有内容都会被阻塞,以确定如何响应主资源。

提前提示是一种 HTTP 状态代码 (103 Early Hints),用于在最终响应之前发送初步 HTTP 响应。这样一来,当服务器忙于生成主资源时,就可以向浏览器发送有关关键子资源(例如网页的样式表、关键 JavaScript)或网页可能会使用的来源的提示。浏览器可以在等待主资源时使用这些提示来预热连接并请求子资源。换句话说,提前提示可帮助浏览器通过提前执行一些工作来利用此类“服务器思考时间”,从而加快网页加载速度。

此图片展示了“提前提示”如何允许网页发送部分响应。
启用提前提示:服务器可以在确定最终响应时提供包含资源提示的部分响应

在某些情况下,Largest Contentful Paint 的性能提升幅度可以达到几百毫秒(如 ShopifyCloudflare 观察到),甚至可以缩短一秒钟,如以下对比图所示:

两个网站的比较。
使用 WebPageTest 在测试网站上对早期提示进行的效果对比(Moto G4 - DSL)

如何使用提前提示

若要充分利用提前提示,第一步是确定热门着陆页,即用户访问您的网站时通常会先访问的页面。如果您有大量用户来自其他网站,则可以是首页,也可以是热门商品详情页面。这些入口点比其他网页更重要,是因为随着用户在您的网站中浏览,提前提示的实用性会降低(也就是说,浏览器在第二次或第三次浏览后更有可能拥有所需的所有子资源)。给用户留下良好的第一印象也是一个好主意!

现在,您已经有了这个优先级着陆页列表,下一步是确定哪些来源或子资源适合使用 preconnectpreload 提示。通常,这些是对关键用户指标(例如 Largest Contentful PaintFirst Contentful Paint)贡献最大的来源和子资源。更具体地说,请查找阻塞渲染的子资源,例如同步 JavaScript、样式表,甚至 Web 字体。同样,请查找托管对关键用户指标有很大贡献的子资源的源站。

另请注意,如果您的主要资源已在使用 preconnectpreload,您可以将这些源或资源视为提前提示的候选对象。如需了解详情,请参阅如何优化 LCP。不过,从 HTML 到早期提示轻率地复制 preconnectpreload 指令可能不是最佳做法

在 HTML 中使用这些方法时,您通常需要 preconnectpreload 预加载扫描器无法在 HTML 中发现的资源,例如,否则会较晚发现的字体或背景图片。对于提前提示,您没有 HTML,因此您可能需要改为preconnect关键网域或preload可能在 HTML 中提前发现的关键资源,例如预加载 main.cssapp.js。此外,并非所有浏览器都支持针对提前提示使用 preload,请参阅浏览器支持

第二步是尽量减少对可能已过时或不再由主资源使用的资源或来源使用提前提示的风险。例如,频繁更新且具有版本号的资源(例如 example.com/css/main.fa231e9c.css)可能不是最佳选择。请注意,此问题并非仅限于提前提示,它适用于任何可能存在的 preloadpreconnect。这类详细信息最好通过自动化或模板处理(例如,手动流程更有可能导致 preload 与使用该资源的实际 HTML 标记之间存在哈希或版本网址不匹配的情况)。

例如,请考虑以下流程:

GET /main.html
Host: example.com
User-Agent: [....] Chrome/103.0.0.0 [...]

服务器预测需要 main.abcd100.css,并建议使用提前提示预加载它:

103 Early Hints
Link: </main.abcd100.css>; rel=preload; as=style
[...]

几秒钟后,系统会提供包含关联 CSS 的网页。很遗憾,此 CSS 资源经常更新,主资源的版本已经比预测的 CSS 资源 (abcd100) 高出 5 个版本 (abcd105)。

200 OK
[...]
<HTML>
<head>
   <title>Example</title>
   <link rel="stylesheet" href="/main.abcd105.css">

一般来说,应尽量使用相当稳定且与主资源结果基本无关的资源和来源。如有必要,您可以考虑将关键资源拆分为两部分:一个稳定的部分,用于与早期提示搭配使用;另一个更具动态性的部分,留待在浏览器收到主资源后再提取:

<HTML>
<head>
   <title>Example</title>
   <link rel="stylesheet" href="/main.css">
   <link rel="stylesheet" href="/experimental.3eab3290.css">

最后,在服务器端,查找已知支持提前提示的浏览器发送的主要资源请求,并立即使用 103 提前提示进行响应。在 103 响应中,添加相关的预连接和预加载提示。主资源准备就绪后,请跟进常规响应(例如,如果成功,则返回 200 OK)。为了实现向后兼容,最好在最终响应中也添加 Link HTTP 标头,甚至可以添加在生成主要资源过程中显而易见的关键资源(例如,如果您遵循了“拆分为两部分”建议,则可以添加关键资源的动态部分)。效果如下所示:

GET /main.html
Host: example.com
User-Agent: [....] Chrome/103.0.0.0 [...]
103 Early Hints
Link: <https://fonts.google.com>; rel=preconnect
Link: </main.css>; rel=preload; as=style
Link: </common.js>; rel=preload; as=script

几分钟后:

200 OK
Content-Length: 7531
Content-Type: text/html; charset=UTF-8
Content-encoding: br
Link: <https://fonts.google.com>; rel=preconnect
Link: </main.css>; rel=preload; as=style
Link: </common.js>; rel=preload; as=script
Link: </experimental.3eab3290.css>; rel=preload; as=style
<HTML>
<head>
   <title>Example</title>
   <link rel="stylesheet" href="/main.css">
   <link rel="stylesheet" href="/experimental.3eab3290.css">
   <script src="/common.js"></script>
   <link rel="preconnect" href="https://fonts.googleapis.com">

浏览器支持

虽然所有主流浏览器都支持 103 早期提示,但可通过早期提示发送的指令因浏览器而异:

预连接支持

浏览器支持

  • Chrome:103。
  • Edge:103.
  • Firefox:120.
  • Safari:17.

预加载支持

浏览器支持

  • Chrome:103。
  • Edge:103.
  • Firefox:123。
  • Safari:不受支持。

Chrome DevTools 还支持 103 早期提示,文档资源中会显示 Link 标头:

显示“Early Hints Headers”的“Network”面板
Chrome 开发者工具中会显示“提前提示”Link标头。

请注意,如需使用早期提示资源,不得在开发者工具中勾选 Disable cache,因为早期提示使用的是浏览器缓存。对于预加载的资源,发起者将显示为 early-hints大小将显示为 (Disk cache)

显示提前提示发起者的“网络”面板
提前提示的资源具有 early-hints 发起器,并从磁盘缓存加载。

这还需要使用可信证书进行 HTTPS 测试。

Firefox(从 v126 开始)在 DevTools 中不支持明确的 103 早期提示,但使用早期提示加载的资源不会显示 HTTP 标头信息,这是一个指示它们是通过早期提示加载的指标。

服务器支持

下面简要介绍了常见开源软件 HTTP 服务器软件对早期提示的支持级别:

更轻松地启用提前提示

如果您使用的是以下任一 CDN 或平台,则可能无需手动实现提前提示。请参阅解决方案提供商的在线文档,了解其是否支持提前提示,或参阅下面的非详尽列表:

如何避免不支持提前提示的客户端出现问题

100 范围内的信息性 HTTP 响应属于 HTTP 标准的一部分,但某些旧版客户端或聊天机器人可能无法处理这些响应,因为在 103 早期提示发布之前,它们很少用于一般网页浏览。

仅针对发送 sec-fetch-mode: navigate HTTP 请求标头的客户端发出 103 早期提示,应可确保仅向了解如何等待后续响应的较新客户端发送此类提示。此外,由于导航请求仅支持提前提示(请参阅当前限制),因此这还具有额外的好处,即避免为其他请求不必要地发送这些提示。

此外,建议仅通过 HTTP/2 或 HTTP/3 连接发送提前提示,大多数浏览器也仅接受通过这些协议发送的提前提示。

高级模式

如果您已将早期提示全面应用于关键着陆页,但仍在寻找更多优化机会,不妨考虑采用以下高级模式。

对于在典型用户体验历程中发出第 n 次网页请求的访问者,您可能需要将提前提示响应调整为适用于网页中更深层次的内容,也就是说,对优先级较低的资源使用提前提示。这可能听起来违反常识,因为我们建议您专注于优先级较高且会阻塞呈现的子资源或来源。不过,在访问者浏览了一段时间后,其浏览器很可能已经有了所有关键资源。从那时起,您不妨将注意力转向优先级较低的资源。例如,这可能意味着使用提前提示加载商品图片,或者仅在用户互动不太频繁时才需要的额外 JS/CSS。

当前限制

以下是 Chrome 中实现的早期提示的限制:

  • 仅适用于导航请求(即顶级文档的主要资源)。
  • 仅支持 preconnectpreload(即不支持 prefetch)。
  • 如果在最终响应中先发送早期提示,然后再进行跨源重定向,则会导致 Chrome 丢弃使用早期提示获取的资源和连接。
  • 使用提前提示预加载的资源会存储在 HTTP 缓存中,并由网页稍后从中检索。因此,只有可缓存的资源才能使用早期提示进行预加载,否则系统会对资源进行两次提取(一次由早期提示,一次由文档)。在 Chrome 中,对于不可信的 HTTPS 证书,HTTP 缓存会被停用(即使您继续加载网页也是如此)。
  • 不支持使用 HTTP <link> 标头预加载响应式图片(使用 imagesrcsetimagesizesmedia,因为视口在文档创建后才会定义。这意味着,103 早期提示无法用于预加载自适应图片,如果用于此目的,可能会加载错误的图片。请参阅关于如何更好地处理此问题的建议讨论

其他浏览器也有类似的限制,并且如前所述,有些浏览器进一步将 103 个早期提示限制为仅限 preconnect

后续操作

根据社区的兴趣,我们可能会通过以下功能增强早期提示的实现:

  • 针对不可缓存资源的提前提示,使用内存缓存(而非 HTTP 缓存)。
  • 在子资源请求中发送的早期提示。
  • 在 iframe 主资源请求中发送的早期提示。
  • 支持在早期提示中预提取。

我们欢迎您提供反馈,告诉我们应优先考虑哪些方面,以及如何进一步改进提前提示。

与 H2/Push 的关系

如果您熟悉已弃用的 HTTP2/Push 功能,可能会想知道提前提示有何不同。虽然早期提示需要浏览器进行一次往返才能开始提取关键子资源,但使用 HTTP2/Push 时,服务器可以开始随响应一起推送子资源。虽然这听起来很棒,但却导致了一个重要的结构性缺点:使用 HTTP2/Push 时,很难避免推送浏览器已有的子资源。这种“过度推送”效应导致网络带宽的使用效率较低,这严重阻碍了性能提升。总体而言,Chrome 数据表明,HTTP2/Push 实际上对整个网络的性能有负面影响。

与之相反,在实践中,提前提示的效果更好,因为它将发送初步响应的能力与提示相结合,让浏览器负责提取或连接到它实际需要的内容。虽然早期提示在理论上无法涵盖 HTTP2/Push 可以解决的所有用例,但我们认为,早期提示是加快导航速度的更实用解决方案。

缩略图图片由 Pierre Bamin 提供。