外观
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, immutablehttp
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 配置需要单独验证,不能只看协议开关是否打开。
常见坑
- HTTP/2 下继续域名分片。连接数增加,缓存命中率下降,收益为负。
- 把服务器推送当银弹。已被主流浏览器移除,且容易推送用户已缓存的资源。
- 所有资源都 preload。会抢占关键资源带宽,preload 应只用于首屏真正需要的资源。
- 忽略缓存键。CDN 缓存键未包含语言、设备或版本参数,导致返回错误内容。
- 只看协议,不看 TTFB。协议优化的是传输效率,服务端慢、重定向多、DNS 差仍会拖垮首屏。
HTTP/3 于 2022 年正式成为 RFC 9114。协议层面的能力已经稳定,但实际收益取决于用户网络、CDN 覆盖和回退策略,接入后要用真实用户数据验证。
小结
HTTP/2 解决了应用层的队头阻塞,HTTP/3 进一步解决了传输层队头阻塞。对前端而言,最重要的转变是:从减少请求数转向优化关键路径和缓存粒度。协议是底座,资源优先级和缓存策略才决定用户实际体验。