更新时间:2026 年 8 月 24 日
HTTP 103 Early Hints 是一种"信息型"HTTP 状态码,允许服务器在最终响应还没生成之前,先把 Link: rel=preload 与 rel=preconnect 头发给浏览器,让 CSS、字体、LCP 图片在后端还在"想"的时候就并行下载。在慢源站场景下,我可以稳定把 LCP 提前 200–400 毫秒。这份 2026 版指南结合 RFC 8297、Cloudflare、NGINX 1.29 以及 Chrome/Firefox/Safari 最新支持情况,给出从边缘配置到 Server-Timing 校准的完整落地方案。
- 103 Early Hints 只在 HTTP/2 与 HTTP/3 上工作,HTTP/1.1 完全不支持。先确认 CDN 与源站已启用现代协议。
- 2026 年浏览器支持率约 93%:Chrome 103+、Edge 103+、Firefox 123+ 支持 preload 与 preconnect;Safari 17+ 仅支持 preconnect。
- Cloudflare 与 NGINX 1.29 mainline 已原生支持 103;Apache 通过 mod_http2 支持,H2O 是最早的参考实现。
- Early Hints 不会真正降低 TTFB,只是提前触发资源下载。用
PerformanceNavigationTiming.finalResponseHeadersStart 与 Server-Timing 才能看到"真实"服务器耗时。
- 响应式图片(
imagesrcset、imagesizes)在 Link 头里通常无效,因为视口在文档解析前尚未确定。主要 preload 的是关键 CSS、字体、单个 LCP 图。
- Chrome 只处理第一次 103 响应,多次发送会被忽略,把所有关键提示合并到一个响应里就好。
什么是 HTTP 103 Early Hints?
103 Early Hints 定义在 RFC 8297 中,是一个"1xx 信息型"响应状态码。服务器在返回最终 200/301/500 之前,可以先发一段带 Link 头的 103 响应,告诉客户端"稍后我大概率会返回这些资源,你可以先去连它们、下它们"。等到源站(PHP、Node、SSR、数据库)终于把 HTML 生成好,再返回真正的 200 响应。
这个机制解决的问题非常具体:动态渲染的页面,源站"思考时间"(server think time)常常在 300ms 到 2s 之间。在传统模型下,浏览器必须等第一字节 HTML 到达、解析 <head>、发现 <link rel="stylesheet">,才会去下载 CSS 与字体。这段串行等待完全被浪费。103 Early Hints 把"资源发现"从"HTML 解析"那一步,提前到了"TCP+TLS 握手结束的一瞬间"。
为什么它必须是新的 1xx 状态码而不是普通头?因为 HTTP 允许一个请求有多次响应头,但只有一次终响应头。1xx 就是"我还没搞定,但先把这些告诉你"的语义槽。这也解释了为什么它只能在 HTTP/2 与 HTTP/3 上工作:HTTP/1.1 无法在一个连接上区分中间响应与终响应。
写到这里必须提醒一点:Early Hints 并不"加速"服务器,它只是把等待时间"利用起来"。如果你的 TTFB 慢是因为 N+1 查询或未命中缓存,103 让浏览器提前把 CSS 拿到手,但用户看到内容的时间仍然被后端阻塞。请把 Early Hints 当作"止血带",不是"手术"。
Early Hints 为什么能把 LCP 拉低 400ms
Cloudflare、Shopify 与 Chrome 团队 2022 至 2024 年的多项 A/B 测试都收敛到同一个数字:LCP 平均改善 200 到 400ms,最好情况下 LCP 图像出现速度提升 35%。这个改善来自三条并行的时间线被"折叠":
- 关键 CSS 下载:如果 CSS 在 CDN 边缘、源站在东岸机房,103 让 CSS 请求在源站还在渲染时就开始,节省一整个 RTT。
- Web 字体连接:
preconnect 到 fonts.gstatic.com 提前完成 DNS+TLS,字体请求本身就少了 200ms 左右。
- LCP 图片 preload:对首屏英雄图使用
as="image" 的 preload,在 HTML 抵达前就把图片放进浏览器的资源加载队列。这个技巧和 用 fetchpriority 和 Priority Hints 把 LCP 降到 1 秒以下里介绍的做法叠加,效果最好。
需要强调一个心理陷阱:103 会让 TTFB 数字看起来更漂亮,因为很多监测工具把"第一个响应头"当作 TTFB。这其实是虚假的胜利。真正影响用户体验的是 FCP 与 LCP。用 Chrome 的 PerformanceNavigationTiming.finalResponseHeadersStart(Chrome 121+)或 Server-Timing 头才能看到源站真实用时。我在后面的"测量"一节会给出可复用的采集脚本。
2026 年浏览器与服务器支持矩阵
下面这张对照表是 2026 年 8 月的实际状态,数据来自 MDN 103 Early Hints 文档与各 CDN 厂商的更新日志:
| 环境 | 版本 | preload | preconnect | 备注 |
| Chrome / Edge | 103+(2022 年 6 月) | ✅ | ✅ | 只处理第一次 103 响应 |
| Firefox | 123+(2024 年 2 月) | ✅ | ✅ | 行为与 Chrome 一致 |
| Safari | 17+ | ❌ | ✅ | 仅 preconnect,preload 忽略 |
| Cloudflare | 全量 | ✅ | ✅ | 需在 Dashboard 或 Cache Rules 开启 |
| NGINX | 1.29 mainline+ | ✅ | ✅ | 需编译时启用 HTTP/2 |
| Apache | 2.4.x + mod_http2 | ✅ | ✅ | 通过 H2EarlyHints 指令 |
| H2O | 2.0+ | ✅ | ✅ | 最早的参考实现 |
| Fastly / Akamai | 全量 | ✅ | ✅ | 通过 VCL / Property Manager |
综合看,全球用户覆盖率约 93%。剩下 7% 主要是 Safari 15/16 与部分嵌入式浏览器,对它们来说 103 会被安全地忽略(当作未知 1xx 处理),完全没有兼容性风险。所以不需要 UA sniffing,全流量启用就行。
如何在 Cloudflare 上启用 Early Hints?
说实话,Cloudflare 的实现最省心:它会异步扫描你的 HTML 响应,把 <link rel="preload"> 和 <link rel="preconnect"> 转成缓存在边缘的 103 响应。下次同 URL 命中时,边缘节点先返回 103,源站还没被打扰。
启用步骤:
- 在 Cloudflare Dashboard 进入 Speed → Optimization → Content Optimization,把 Early Hints 切到 On。
- 在 HTML 里正常写
<link rel="preload" as="image" href="/hero.avif" fetchpriority="high"> 与 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>。
- Cloudflare 会在第一次访问时"学习"这些标签,第二次起把它们提升为 103 响应。
如果你想跳过 HTML 扫描、直接由源站发 Link 头,也可以。Cloudflare 会透传 Link 头并转成 103。这对 SSR 或 API 网关型架构更灵活:
// Next.js 15 middleware.ts
import { NextResponse } from 'next/server'
export function middleware() {
const res = NextResponse.next()
res.headers.set(
'Link',
'</hero.avif>; rel=preload; as=image; fetchpriority=high, ' +
'</fonts/inter.woff2>; rel=preload; as=font; type="font/woff2"; crossorigin, ' +
'<https://cdn.example.com>; rel=preconnect; crossorigin'
)
return res
}
注意 Link 头里 URL 用 <...> 尖括号包围,多个提示用逗号分隔,属性用分号分隔。这是 RFC 8288 的 Link 语法,写错就静默失败。想复习 CDN 侧的预取与预渲染策略,可以配合看 Speculation Rules API 完全指南。
如何在 NGINX 中配置 103 Early Hints?
NGINX 直到 1.29 mainline(2025 年)才原生支持 103。老版本请升级,或者改用 OpenResty + lua-nginx-module 手写。1.29+ 的最小可用配置是这样的:
server {
listen 443 ssl http2;
http2_early_hints on; # 关键开关
server_name example.com;
location / {
# 声明会在最终响应中带的关键资源
add_early_hint Link "</static/critical.css>; rel=preload; as=style";
add_early_hint Link "</static/inter.woff2>; rel=preload; as=font; type=\"font/woff2\"; crossorigin";
add_early_hint Link "<https://api.example.com>; rel=preconnect";
proxy_pass http://app_upstream;
}
}
逻辑很直白:请求进入 location 后,NGINX 立即写出 103 + Link 头到 socket,然后再把请求代理到 upstream。upstream 生成响应期间,浏览器已经收到了 103 并开始 preload。等 upstream 返回 200,NGINX 再把最终响应转发出去。
三个必检项:
- 协议:
listen 443 ssl http2;,HTTP/1.1 完全无效。生产环境建议再加 listen 443 quic reuseport; 启用 HTTP/3。
- 上游超时:
proxy_read_timeout 保持默认即可,103 不受此影响。
- 反代链:如果 NGINX 前面还有 CDN,确认 CDN 允许透传 1xx。Cloudflare 与 Fastly 默认允许,某些企业 WAF 会吞掉 1xx,需要在 WAF 白名单里放行
103。
Link 头是 103 里唯一被浏览器采纳的字段。写错语法不会报错,只会被静默忽略。这也是我见过最常见的 Early Hints "配置了但没生效"根因。核心规则:
- URL 必须用尖括号包围:
</hero.avif>,不能省略。
- 参数用分号分隔:
rel=preload; as=image; type="image/avif"。
- 多个 Link 用逗号分隔,或者发多个
Link: 头(等价)。
- as 属性必须匹配:CSS 用
as=style、字体用 as=font 且必须 crossorigin、脚本用 as=script、图片用 as=image。缺 as 或值不对,浏览器会重新下载一次,等于没优化。
- 只 preload 首屏必需资源:3 到 5 个是上限。preload 太多会挤占带宽,反而拖累 LCP。
响应式图片是最容易踩坑的:Link 头里的 imagesrcset、imagesizes、media 参数目前在几乎所有浏览器上都不生效,因为 Early Hints 阶段浏览器还没解析文档,也就不知道视口宽度。想给 LCP 图使用 srcset,我建议放弃 103 preload,改用 HTML 里的 <link rel="preload" imagesrcset="...">。这些第三方资源加载与请求预算的关系,可以参考 第三方脚本性能优化完全指南(2026)。
如何测量真实的 TTFB 而不被 Early Hints 掩盖?
103 会污染很多监测工具的 TTFB。默认情况下,PerformanceNavigationTiming.responseStart 拿到的是"任何响应头到达",包括 103。Chrome 121 引入了 finalResponseHeadersStart,专门指向最终 200 响应头到达的时间。这个字段才是真实 TTFB。
我推荐的采集代码,可直接放进 RUM 脚本:
const nav = performance.getEntriesByType('navigation')[0]
// 传统 TTFB:会被 103 拉低
const perceivedTTFB = nav.responseStart - nav.requestStart
// 真实服务器耗时(Chrome 121+,其它浏览器回退到 responseStart)
const realTTFB = (nav.finalResponseHeadersStart ?? nav.responseStart) - nav.requestStart
// 103 提前了多少
const earlyHintsGain = realTTFB - perceivedTTFB
navigator.sendBeacon('/rum', JSON.stringify({
perceivedTTFB, realTTFB, earlyHintsGain,
url: location.pathname,
}))
再配合源站发 Server-Timing 头:
Server-Timing: db;dur=143, render;dur=58, cache;desc="MISS"
这样在 DevTools 的 Network → Timing 面板就能看到"数据库 143ms、渲染 58ms、缓存 MISS"的分解。真正的性能瓶颈立刻现形,避免"Early Hints 数字漂亮但用户还在等 2 秒"的假胜利。
Early Hints、preload、prerender 的差异
这三者常被混为一谈,但在时间轴上定位完全不同。用一个表快速区分:
| 技术 | 触发时机 | 解决的问题 | 成本 |
| 103 Early Hints | 服务器"思考时间"里 | 提前 CSS/字体/LCP 图下载 | 近乎为零,只多 1 个响应 |
HTML <link rel=preload> | HTML 已到达、head 解析时 | 缩短资源发现延迟 | 需要 HTML 先到 |
| Speculation Rules prerender | 用户还没点击时 | 整页 0ms 导航 | 浏览器全量渲染另一个页面 |
rel=prefetch | 空闲时间 | 下一跳静态资源 | 可能白下载 |
组合策略:103 Early Hints + fetchpriority=high 的 preload + Speculation Rules prerender 是 2026 年最激进也最有效的三段式打法。103 解决"当前页首屏",preload 兜底不支持 103 的浏览器,Speculation Rules 让下一跳变成 0ms。这个组合我在客户站点上一起上线过一次,p75 LCP 从 2.9s 降到 1.1s。老板那天特别高兴。
上线 103 常见的坑,按频率排序:
- 协议仍是 HTTP/1.1:企业内网、老版本 K8s Ingress 或反代默认 1.1,103 完全无效。DevTools Network 里看 Protocol 列必须是 h2/h3。
- WAF 或代理吞掉 1xx:某些安全网关不理解 1xx,会直接丢弃。抓包(Wireshark 或
curl --http2 -v)确认客户端收到 103。
- CSP 冲突:Chrome 只处理第一个 103,因为多个 103 可能带不一致的 CSP。如果你的 CDN 与源站都想发,会有一个被忽略。选一处即可。
- Link 语法错误:忘了尖括号、
as 值错、字体 preload 没加 crossorigin,都是静默失败。可以用 DebugBear 的 Early Hints 检查工具验证。
- Preload 太多:超过 5 个反而拖慢 LCP。首屏英雄图 + 1 张关键 CSS + 1 个字体 + 1 个 preconnect 已足够。
- 响应式图片:
imagesrcset 在 Link 头里几乎无效,放弃或改用 HTML preload。
- Bot 触发缓存污染:Cloudflare 的 HTML 扫描模式会把爬虫路径也当作学习样本,某些页面永远缓存到"不该 preload"的资源。可用 Cache Rules 排除。
我的排错顺序通常是:curl -I --http2 https://example.com 看是否返回 HTTP/2 103,然后在 DevTools Network 面板 Response Headers 里确认能否看到 "Early Hints" 分组,最后在 RUM 里对比 earlyHintsGain 分布。三步下来 90% 的问题都能定位。
常见问题
HTTP 103 状态码到底是什么?
103 是 RFC 8297 定义的信息型响应状态码,服务器可以在最终响应准备好之前先发它,用来在 Link 头里给浏览器提示要提前 preload 或 preconnect 的资源。它必须运行在 HTTP/2 或 HTTP/3 上,HTTP/1.1 完全不支持。
Cloudflare 支持 Early Hints 吗?
支持,而且是全量免费功能。在 Speed → Optimization → Content Optimization 里打开 Early Hints,Cloudflare 会自动扫描你 HTML 里的 <link rel=preload>/preconnect 标签,或透传源站的 Link 头,把它们转成缓存在边缘的 103 响应。
Safari 支持 103 Early Hints 吗?
Safari 17+ 只是部分支持:只处理 rel=preconnect,忽略 rel=preload。这意味着字体域名的 DNS+TLS 会提前完成,但关键 CSS 不会被提前下载。对 Safari 用户,preload 的收益暂时拿不到。
Early Hints 能把 LCP 降低多少?
Cloudflare 与 Chrome 团队的实测数据显示,动态渲染的页面 LCP 平均改善 200 到 400ms,LCP 图像出现速度最高提升 35%。改善幅度取决于源站的"思考时间":源站越慢,103 能"填"进去的时间越多。
如何在 NGINX 里启用 103 Early Hints?
需要 NGINX 1.29 mainline 或更新版本。在 server 块加 http2_early_hints on;,在 location 块用 add_early_hint Link "</style.css>; rel=preload; as=style"; 声明要提前的资源,同时确保 listen 443 ssl http2; 已启用 HTTP/2。
103 Early Hints 会真正降低 TTFB 吗?
不会。103 只是把资源发现提前,源站生成 HTML 的耗时并没有变。它让某些工具显示的 TTFB "看起来"更低,但真实 TTFB 要用 PerformanceNavigationTiming.finalResponseHeadersStart 或 Server-Timing 头才能准确测量。请把它当作 LCP 优化手段,不是 TTFB 优化手段。