HTTP/2 Server Push 退场后的资源提示实战:preload、preconnect 与 103 Early Hints 怎么选

从 HTTP/2 Server Push 的退场说起

如果你在 2018 年前后做过网站性能优化,一定见过 HTTP/2 Server Push(服务器推送)——服务器可以在浏览器请求 HTML 的同时,主动把 CSS、JS、字体"推"给客户端,理论上省去一轮请求往返。当时它被誉为 HTTP/2 最亮眼的特性之一,Nginx 里一行 http2_push 就能开启。

但到了 2022 年,Chrome 正式移除了对 Server Push 的支持,其他浏览器也陆续跟进。为什么一个看起来这么美好的特性会被淘汰?原因很现实:服务器很难准确知道浏览器缓存里已经有什么,导致大量重复推送,把本来能省下的带宽反而浪费掉了;而 HTTP/1.1 时期为了绕过每域名 6 连接限制而发明的"内联资源"做法,以及更聪明的资源优先级提示,已经能更好、更可控地达到同样目的。Push 是一次"服务器单向猜测",而现代 Web 性能优化更倾向于让浏览器基于明确的提示自己做决策。

Server Push 退场后,接力棒交到了 资源提示(Resource Hints) 手里:preload、preconnect、dns-prefetch、prefetch,以及全新的 103 Early Hints。本文就系统讲清楚这几样东西各自解决什么问题、优先级怎么排、以及个人站长站点上最实用的组合用法,并给出一个真实的前后对比案例。

四种资源提示:先搞清楚它们各管一段

很多人把资源提示混为一谈,其实它们对应的是浏览器加载一个资源所经历的不同阶段。理解这一点,就知道该在什么时候用哪个:

  • dns-prefetch:只做 DNS 解析。当你知道接下来要连某个第三方域名(例如 CDN、字体服务、统计脚本),但不一定马上要用,用它可以提前把域名解析好。开销极小,是"最便宜"的提示。
  • preconnect:在 DNS 解析之外,还提前完成 TCP 三次握手和 TLS 握手。这个成本比 DNS 高不少,但收益也大——因为建立连接往往占了第三方资源加载延迟的一大半。适合你确定要用的关键第三方源。
  • preload:告诉浏览器"这个当前页面马上就会用到的资源,请以高优先级提前下载",并且要声明资源类型(as)。它针对的是具体资源,而不是域名。常用于首屏关键的字体、CSS、首屏大图。
  • prefetch:低优先级的"预取",用于下载下一个页面可能用到的资源。它不会和当前页面的资源抢带宽,适合用户极可能点击的下一页。

一句话总结优先级:preload(本页关键) > preconnect(提前建连) > dns-prefetch(提前解析) > prefetch(下一页)。用错层级会适得其反——比如把本页首屏 CSS 写成 prefetch,它就变成低优先级,反而拖慢了首屏。

preload 的正确姿势:as 属性、crossorigin 与字体的大坑

preload 是最强也最容易写错的一个。基本写法:

<link rel="preload" href="/fonts/site.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/css/critical.css" as="style">
<link rel="preload" href="/img/hero.webp" as="image">

三个必须记住的坑:

  • as 不能省。它告诉浏览器资源的类型,浏览器据此决定用哪个优先级、是否需要内容协商。as 写错会导致 preload 白做,控制台还会报 warning。
  • 字体必须带 crossorigin。字体文件属于跨域资源(即使同域,浏览器的 CORS 规则也把它当跨域处理),不带 crossorigin 的字体 preload 会被下载两次——一次是 preload 的、一次是真正使用时的——等于白白浪费一份带宽。这是 preload 里最常见的翻车点。
  • preload 了就必须真正用到。如果你 preload 了一个资源,但页面上并没有以相同方式使用它(比如 preload 了 CSS 却在 JS 里动态插入),浏览器会警告 "resource was preloaded but not used within a few seconds",且这次 preload 就是纯浪费。

还要注意:preload 会提升资源优先级,抢占带宽。如果你一次 preload 太多东西,反而会让真正关键的请求被推迟。原则是——只 preload 首屏渲染必需的、且是"关键路径上晚才发现"的资源,通常不超过 3 到 5 个。

preconnect 与 dns-prefetch:给第三方源提前建连

第三方资源(CDN、字体、统计、客服组件)的延迟,很大一部分耗在"连接建立"上。用 preconnect 提前把这部分做掉,能显著缩短它们的加载时间:

<link rel="preconnect" href="https://cdn.example.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="dns-prefetch" href="https://cdn.example.com">

几个实践要点:

  • 跨域的 preconnect 也要 crossorigin。原因是跨域连接需要用匿名 CORS 模式建立,和普通请求的连接不是同一条,少了 crossorigin 就白连了。
  • 数量要克制。每个 preconnect 都会占用一个连接并消耗 CPU 做 TLS 握手。一般控制在 4 个以内,只给最关键、最确定要用的第三方域。对于不太确定会不会用的域,用 dns-prefetch 就够了。
  • 两个可以配对使用。老浏览器不支持 preconnect 时,dns-prefetch 作为兜底,两者同时写上没有副作用。
  • 别忘了连接用完后及时释放。如果 preconnect 后一个连接闲置超过一定时间,浏览器会自动关闭,所以 preconnect 应该放在 <head> 靠前位置,尽早生效。

103 Early Hints:Server Push 真正的继任者

Server Push 退场后,官方推荐的替代方案是 HTTP 103 Early Hints。它的思路很巧妙:当用户请求 HTML 时,服务器在真正算出 HTML 之前,先返回一个临时的 103 状态码响应,里面带上 Link rel="preload" 头,告诉浏览器"这些资源你可以现在就去下载了"。等服务器把 HTML 准备好,再返回正式的 200。

它和 Server Push 的本质区别在于:Early Hints 只是"提示",最终下不下、下几份由浏览器决定,因此不会像 Push 那样重复推送浪费带宽。浏览器可以结合自己的缓存状态做出判断,这正是 Push 做不到的。

在 Nginx 里启用 Early Hints 需要较新版本(1.29+)并显式开启:

# 全局开启 Early Hints
http {
    early_hints on;
    server {
        location = /index.html {
            add_header Link "</css/critical.css>; rel=preload; as=style" always;
            add_header Link "</js/app.js>; rel=preload; as=script" always;
        }
    }
}

需要说明的是,Early Hints 依赖后端应用(或反向代理)能"边算边发"——对于纯静态文件、Nginx 瞬间就能读出来的场景,收益不大,因为 HTML 本身就返回得很快,没有多少可以提前利用的窗口。它真正发光的地方是后端渲染耗时较长的站点(比如带数据库查询的 PHP 页面),此时能抢出几百毫秒让浏览器提前下资源。对绝大多数个人静态站来说,把 preload 写对,收益就已经覆盖了 Early Hints 能带来的好处。

一次真实的优化复盘:首屏 LCP 从 3.4s 降到 1.6s

我运营的一个图文站,首页首屏的 LCP(最大内容绘制)长期在 3.4 秒左右,Lighthouse 上被标红。用 Chrome DevTools 的 Performance 面板录制了一次加载,按时间轴逐段看,瓶颈很清楚:

  1. 服务器返回的 HTML 里,首屏 hero 大图是用 <img src> 直接引用的,但它在 HTML 里出现的位置比较靠后,浏览器要先把前面的 CSS 和一部分 JS 解析完才发现它,导致大图下载起步太晚。
  2. 页面用了一个自定义的中文字体,字体文件在 CSS 里通过 @font-face 引入,没有 preload,导致文字要等字体下载完才渲染,出现了明显的文本闪烁。
  3. 页头引入了两个第三方资源(一个 CDN 上的图标库、一个统计脚本),它们的域名没有 preconnect,每次加载都要现做 DNS 和 TLS 握手。

对应的三步优化:

<!-- 1. 首屏大图 preload,并声明类型 -->
<link rel="preload" href="/img/hero.webp" as="image">

<!-- 2. 关键字体 preload,务必带 crossorigin -->
<link rel="preload" href="/fonts/site.woff2" as="font" type="font/woff2" crossorigin>

<!-- 3. 给两个关键第三方域 preconnect -->
<link rel="preconnect" href="https://cdn.example.com" crossorigin>
<link rel="preconnect" href="https://statics.example.net">

同时,我把字体文件的 font-display 改成了 swap(先用系统字体渲染、字体加载完再切换),避免文字被字体阻塞而白屏。改动上线后用真实用户监控(RUM)观察了一周,LCP 的 75 分位数从 3.4 秒降到了 1.6 秒左右,效果非常稳定。事后回看,最关键的一步其实是首屏大图 preload——它让浏览器在解析 HTML 的极早期就开始下载最大的那张图;而字体 preload 解决的是渲染阻塞和闪烁;preconnect 则削掉了第三方资源的连接建立时间。三者各司其职,缺一不可。

这个案例也印证了一个规律:资源提示优化不是"堆标签",而是先用量化指标(LCP、首屏时间)和录制工具找到真正的瓶颈,再针对性地补上那一条提示。盲目往页面里塞 preload,很可能什么都没改善,还因为抢占带宽而让情况变糟。

个人站的资源提示落地清单

最后给一份可以直接对照执行的清单:

  • 先测量再优化。用 Lighthouse 或真实用户监控定位首屏瓶颈,别凭感觉。
  • 只 preload 首屏关键、且"晚被发现"的资源(首屏大图、关键 CSS、关键字体),数量控制在 3 到 5 个以内。
  • preload 字体永远带 crossorigin,否则会被下载两次。
  • 给确定要用的第三方域加 preconnect(跨域的也加 crossorigin),最多 4 个;不确定的用 dns-prefetch 兜底。
  • prefetch 只用于用户很可能访问的下一页,不要用来优化当前页。
  • 字体加 font-display: swap,避免渲染阻塞和文字闪烁。
  • 不要再用 HTTP/2 Server Push(已从主流浏览器移除),后端渲染慢的站点可以考虑 HTTP 103 Early Hints。
  • 上线后用 RUM 对比优化前后的 LCP / 首屏时间,确认改动真的有效,而不是停留在"我加了标签"。

Server Push 的退场并不是性能优化的倒退,而是一次思路的转变:从"服务器替浏览器做决定"变成"给浏览器准确的提示,让它自己决策"。资源提示这套组合拳,正是这一思路的落地。对个人站长来说,它们全部只涉及几行 <head> 里的标签,成本极低;但用对了、用准了,就能实打实地把首屏时间砍掉一大截,而首屏时间又直接影响跳出率和搜索排名。先量化、再提示、后验证——记住这三步,你就能避开绝大多数性能优化的弯路。

Last modification:October 16th, 2026 at 07:39 pm

Leave a Comment