了解服务器如何向浏览器发送有关关键子资源的提示。
什么是早期提示?
随着时间的推移,网站变得更加复杂。因此,服务器需要执行一些繁琐的工作(例如访问数据库或 CDN 访问源服务器)来为请求的网页生成 HTML,这并不奇怪。遗憾的是,这种“服务器思考时间”会导致浏览器在开始呈现网页之前出现额外的延迟。事实上,在服务器准备响应期间,连接实际上处于空闲状态。
Early Hints 是一个 HTTP 状态代码 (103 Early Hints
),用于在最终响应之前发送初步 HTTP 响应。这样一来,当服务器忙于生成主资源时,就可以向浏览器发送有关关键子资源(例如网页的样式表、关键 JavaScript)或网页可能会使用的来源的提示。浏览器可以在等待主资源时使用这些提示来预热连接并请求子资源。换句话说,提前提示可帮助浏览器通过提前执行一些工作来利用此类“服务器思考时间”,从而加快网页加载速度。
在某些情况下,Largest Contentful Paint 的性能提升幅度可以达到几百毫秒(如 Shopify 和 Cloudflare 所观察到),甚至可以缩短一秒钟,如以下对比图所示:
如何使用提前提示
若要充分利用提前提示,第一步是确定热门着陆页,即用户访问您的网站时通常会先访问的页面。如果您有大量用户来自其他网站,则可以是首页,也可以是热门商品详情页面。这些入口点比其他页面更重要的原因在于,当用户浏览您的网站时,Early Hints 的实用性会降低(即,浏览器更有可能在后续的第二次或第三次导航中拥有所需的所有子资源)。给人留下良好的第一印象也始终是一个好主意!
现在,您已经有了这个优先级着陆页列表,下一步是确定哪些来源或子资源适合使用 preconnect
或 preload
提示。通常,这些资源是对关键用户指标贡献最大的来源和子资源,例如 Largest Contentful Paint 或 First Contentful Paint。更具体地说,请查找阻塞渲染的子资源,例如同步 JavaScript、样式表,甚至 Web 字体。同样,请寻找托管对关键用户指标贡献很大的子资源的源。
另请注意,如果您的主要资源已在使用 preconnect
或 preload
,您可以将这些源或资源视为提前提示的候选对象。如需了解详情,请参阅如何优化 LCP。不过,从 HTML 到早期提示轻率地复制 preconnect
和 preload
指令可能不是最佳做法。
在 HTML 中使用这些方法时,您通常需要 preconnect
或 preload
预加载扫描器无法在 HTML 中发现的资源,例如,否则会较晚发现的字体或背景图片。对于提前提示,您没有 HTML,因此您可能需要改为preconnect
关键网域或preload
可能在 HTML 中提前发现的关键资源,例如预加载 main.css
或 app.js
。此外,并非所有浏览器都支持针对提前提示使用 preload
,请参阅浏览器支持。
第二步是尽量避免对可能已过时或不再由主资源使用的资源或来源使用提前提示。例如,频繁更新且具有版本号的资源(例如 example.com/css/main.fa231e9c.css
)可能不是最佳选择。请注意,此问题并非仅适用于提前提示,它适用于任何可能存在的 preload
或 preconnect
。这类详细信息最好通过自动化或模板处理(例如,手动流程更有可能导致 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 DevTools 还支持 103 早期提示,文档资源中会显示 Link
标头:
请注意,如需使用早期提示资源,不得在开发者工具中勾选 Disable cache
,因为早期提示使用的是浏览器缓存。对于预加载的资源,启动器会显示为 early-hints
,大小会显示为 (Disk cache)
:
这还需要使用可信证书进行 HTTPS 测试。
Firefox(从 v126 开始)在 DevTools 中不支持明确的 103 早期提示,但使用早期提示加载的资源不会显示 HTTP 标头信息,这是一个指示它们是通过早期提示加载的指标。
服务器支持
下面简要介绍了常见开源软件 HTTP 服务器软件对早期提示的支持级别:
更轻松地启用“早期提示”
如果您使用的是以下某个 CDN 或平台,则可能不需要手动实现早期提示。请参阅解决方案提供商的在线文档,了解它是否支持早期提示,或者参阅此处的非详尽列表:
如何避免不支持 Early Hints 的客户端出现问题
100 范围内的信息性 HTTP 响应属于 HTTP 标准的一部分,但某些旧版客户端或聊天机器人可能无法处理这些响应,因为在 103 早期提示发布之前,它们很少用于一般网页浏览。
仅针对发送 sec-fetch-mode: navigate
HTTP 请求标头的客户端发出 103 早期提示,应可确保仅向了解如何等待后续响应的较新客户端发送此类提示。此外,由于导航请求仅支持提前提示(请参阅当前限制),因此这还具有额外的好处,即避免在其他请求中不必要地发送这些提示。
此外,建议仅通过 HTTP/2 或 HTTP/3 连接发送提前提示,大多数浏览器也仅接受通过这些协议发送的提前提示。
高级模式
如果您已将“早期提示”功能全面应用于关键着陆页,并且发现自己在寻求更多机会,那么您可能对以下高级模式感兴趣。
对于在典型用户体验历程中发出第 n 次网页请求的访问者,您可能需要将提前提示响应调整为适用于网页中更低级别且更深层的内容,也就是说,对优先级较低的资源使用提前提示。这可能听起来违反常识,因为我们建议您专注于优先级较高且会阻塞呈现的子资源或来源。不过,在访问者浏览一段时间后,其浏览器很可能已经拥有所有关键资源。从那时起,您不妨将注意力转向优先级较低的资源。例如,这可能意味着使用提前提示加载商品图片,或者仅在用户互动不太频繁时才需要的额外 JS/CSS。
当前限制
在 Chrome 中实现的“早期提示”功能存在以下局限性:
- 仅适用于导航请求(即顶级文档的主要资源)。
- 仅支持
preconnect
和preload
(即不支持prefetch
)。 - 如果在最终响应中先发送早期提示,然后再进行跨源重定向,则会导致 Chrome 丢弃使用早期提示获取的资源和连接。
- 使用提前提示预加载的资源会存储在 HTTP 缓存中,并由网页稍后从中检索。因此,只有可缓存的资源才能使用早期提示预加载,否则系统会对资源进行两次提取(一次由早期提示,另一次由文档)。在 Chrome 中,对于不可信的 HTTPS 证书,HTTP 缓存会被停用(即使您继续加载网页也是如此)。
- 不支持使用 HTTP
<link>
标头预加载自适应图片(使用imagesrcset
、imagesizes
或media
),因为在创建文档之前不会定义视口。这意味着,103 早期提示无法用于预加载自适应图片,并且在用于此目的时可能会加载错误的图片。请参阅关于如何更好地处理此问题的建议讨论。
其他浏览器也有类似的限制,并且如前所述,有些浏览器进一步将 103 个早期提示限制为仅限 preconnect
。
后续操作
根据社区的兴趣,我们可能会通过以下功能增强早期提示的实现:
- 针对不可缓存资源的早期提示,使用内存缓存(而非 HTTP 缓存)。
- 在子资源请求中发送的早期提示。
- 在 iframe 主资源请求中发送的早期提示。
- 支持在早期提示中预提取。
我们欢迎您提供反馈,告诉我们应优先考虑哪些方面,以及如何进一步改进早期提示。
与 H2/Push 的关系
如果您熟悉已弃用的 HTTP2/Push 功能,可能会想知道提前提示有何不同。虽然早期提示需要浏览器进行一次往返才能开始提取关键子资源,但使用 HTTP2/Push 时,服务器可以开始随响应一起推送子资源。虽然这听起来很棒,但却导致了一个重要的结构性缺点:使用 HTTP2/Push 时,很难避免推送浏览器已有的子资源。这种“过度推送”效应导致网络带宽使用效率降低,显著阻碍了性能优势。总体而言,Chrome 数据表明,HTTP2/Push 实际上对整个网络的性能有负面影响。
与之相反,在实践中,提前提示的效果更好,因为它将发送初步响应的能力与提示相结合,让浏览器负责提取或连接到它实际需要的内容。虽然在理论上,提前提示并不能涵盖 HTTP2/Push 可以解决的所有用例,但我们认为,提前提示是加快导航速度的更实用解决方案。
缩略图:Pierre Bamin。