Skip to content

HTTP/2 与 HTTP/3:前端资源加载的底层逻辑 ​

很多前端优化经验来自 HTTP/1.1 时代的限制:域名分片、雪碧图、合并 JS 和 CSS、内联小图片。这些做法在 HTTP/2 和 HTTP/3 下有的仍然有效,有的已经变成负担。

协议变了,资源加载策略也要跟着变。这篇文章从 HTTP/1.1 的瓶颈出发,梳理 HTTP/2、HTTP/3 的能力边界,以及它们对前端工程的实际影响。

HTTP/1.1 的瓶颈 ​

HTTP/1.1 的核心问题是队头阻塞。一个 TCP 连接上,请求必须按顺序处理:

  • 浏览器对同一域名通常只开 6 个并发连接。
  • 一个慢请求会占住连接,后续请求排队。
  • 请求数越多,连接竞争越严重。

于是诞生了一系列优化手段:

  • 域名分片:把资源分散到多个子域名,突破单域名连接数限制。
  • 资源合并:把多个 JS/CSS 合并成一个文件,减少请求数。
  • 雪碧图 / Base64 内联:减少图片请求。
  • 按域名预连接:preconnect 多个资源域。

这些手段的共同代价是缓存粒度变粗:一个文件改动,整个合并包失效。

HTTP/2:一个连接上的多路复用 ​

HTTP/2 引入二进制分帧层,把请求和响应拆成帧,在同一个 TCP 连接上并发传输:

  • 多路复用:多个请求互不阻塞,不再需要域名分片。
  • 头部压缩:HPACK 压缩重复的请求头,减少传输体积。
  • 流优先级:理论上可以让关键资源优先传输,但各浏览器实现差异大,实际效果有限。
  • 服务器推送:曾经被视为革命性能力,但因为缓存和优先级问题,Chrome 已在 2022 年移除支持,不应再作为方案依赖。

HTTP/2 仍然跑在 TCP 上,所以传输层队头阻塞依然存在:一个 TCP 包丢失,后续所有流都要等待重传。网络质量差时,这个问题会被放大。

HTTP/3:把传输层换成 QUIC ​

HTTP/3 的核心是把传输层从 TCP 换成基于 UDP 的 QUIC:

  • 独立流:每个请求是独立的 QUIC 流,一个流丢包不影响其他流。
  • 内置 TLS 1.3:加密与传输握手合并,通常 1-RTT,重连可以 0-RTT。
  • 连接迁移:用连接 ID 标识连接,手机从 Wi-Fi 切到蜂窝网络时不必重新握手。
  • 更好的弱网表现:丢包恢复和拥塞控制都在用户态实现,可以更快迭代。

HTTP/3 的代价是 UDP 在某些企业网络、代理环境中会被限制,需要 TCP 回退。部署时通常同时开启 HTTP/2 和 HTTP/3,由浏览器自动协商。

协议对前端策略的影响 ​

域名分片应该退出 ​

HTTP/2 下,多域名会拆散连接复用,反而增加 DNS、TCP 和 TLS 握手成本。现代站点应尽量让静态资源走同一个域名或同一个 CDN,并通过 CDN 的连接复用能力优化跨域资源。

合并资源不再是默认选项 ​

请求数不再是主要瓶颈,缓存粒度更重要。更合理的策略是:

  • 按路由和功能拆分 bundle,让不常变化的第三方依赖单独成 chunk。
  • 利用长期缓存:带 hash 的静态资源设置 immutable。
  • 关键 CSS 内联,非关键 CSS 异步加载。
http
Cache-Control: public, max-age=31536000, immutable
http
Cache-Control: no-cache
ETag: "a1b2c3"

immutable 告诉浏览器在有效期内不要发起协商请求,适合文件名带内容 hash 的资源。HTML 入口则使用 no-cache,保证每次都能拿到最新引用。

优先级比数量更重要 ​

无论 HTTP/2 还是 HTTP/3,带宽和主线程资源都是有限的。真正影响 LCP 的是关键资源能否尽早被发现和传输:

html
<link rel="preconnect" href="https://cdn.example.com" crossorigin />
<link rel="preload" as="image" href="/hero.avif" fetchpriority="high" />
<script src="/app.js" type="module"></script>

preconnect 提前完成 DNS、TCP、TLS 握手;preload 提前发起资源请求;fetchpriority 调整资源相对优先级。三者要配合使用,避免把所有资源都标成高优先级。

利用 103 Early Hints ​

服务器可以在最终响应前先返回 103 Early Hints,让浏览器提前预加载关键资源:

js
// Node.js 18.11+
res.writeEarlyHints({
  link: ['</app.css>; rel=preload; as=style', '</hero.avif>; rel=preload; as=image']
})

这比在 HTML 中写 <link rel="preload"> 更早,适合 TTFB 较长的动态页面。需要 CDN 或源站支持,否则不会生效。

服务端配置示例 ​

以 Nginx 为例,同时开启 HTTP/2 和 HTTP/3,并设置静态资源缓存:

nginx
server {
  listen 443 ssl;
  http2 on;                    # Nginx 1.25.1+
  add_header Alt-Svc 'h3=":443"; ma=86400' always;

  location /assets/ {
    add_header Cache-Control "public, max-age=31536000, immutable";
    try_files $uri =404;
  }

  location / {
    add_header Cache-Control "no-cache";
    try_files $uri /index.html;
  }
}

Alt-Svc 告诉浏览器该站点支持 HTTP/3。浏览器首次仍会走 HTTP/2,后续请求再尝试 QUIC。HTTP/3 的证书、端口和 CDN 配置需要单独验证,不能只看协议开关是否打开。

常见坑 ​

  1. HTTP/2 下继续域名分片。连接数增加,缓存命中率下降,收益为负。
  2. 把服务器推送当银弹。已被主流浏览器移除,且容易推送用户已缓存的资源。
  3. 所有资源都 preload。会抢占关键资源带宽,preload 应只用于首屏真正需要的资源。
  4. 忽略缓存键。CDN 缓存键未包含语言、设备或版本参数,导致返回错误内容。
  5. 只看协议,不看 TTFB。协议优化的是传输效率,服务端慢、重定向多、DNS 差仍会拖垮首屏。

HTTP/3 于 2022 年正式成为 RFC 9114。协议层面的能力已经稳定,但实际收益取决于用户网络、CDN 覆盖和回退策略,接入后要用真实用户数据验证。

小结 ​

HTTP/2 解决了应用层的队头阻塞,HTTP/3 进一步解决了传输层队头阻塞。对前端而言,最重要的转变是:从减少请求数转向优化关键路径和缓存粒度。协议是底座,资源优先级和缓存策略才决定用户实际体验。

基于 VitePress 构建 · 持续更新中