103 Early Hints 完全指南(2026):用 Cloudflare 与 NGINX 把 LCP 提前 400ms

在源站还在生成 HTML 的那几百毫秒里,103 Early Hints 让浏览器把关键 CSS、字体和 LCP 图先下载起来。本文用 Cloudflare、NGINX 1.29 与 Server-Timing 的真实配置,教你把 p75 LCP 稳定拉低 200 到 400ms,并避开 6 个最常见的静默失败坑。

更新时间:2026 年 8 月 24 日

HTTP 103 Early Hints 是一种"信息型"HTTP 状态码,允许服务器在最终响应还没生成之前,先把 Link: rel=preloadrel=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.finalResponseHeadersStartServer-Timing 才能看到"真实"服务器耗时。
  • 响应式图片(imagesrcsetimagesizes)在 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 字体连接preconnectfonts.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 厂商的更新日志:

环境版本preloadpreconnect备注
Chrome / Edge103+(2022 年 6 月)只处理第一次 103 响应
Firefox123+(2024 年 2 月)行为与 Chrome 一致
Safari17+仅 preconnect,preload 忽略
Cloudflare全量需在 Dashboard 或 Cache Rules 开启
NGINX1.29 mainline+需编译时启用 HTTP/2
Apache2.4.x + mod_http2通过 H2EarlyHints 指令
H2O2.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,源站还没被打扰。

启用步骤:

  1. 在 Cloudflare Dashboard 进入 Speed → Optimization → Content Optimization,把 Early Hints 切到 On。
  2. 在 HTML 里正常写 <link rel="preload" as="image" href="/hero.avif" fetchpriority="high"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  3. 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 头里的 imagesrcsetimagesizesmedia 参数目前在几乎所有浏览器上都不生效,因为 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 常见的坑,按频率排序:

  1. 协议仍是 HTTP/1.1:企业内网、老版本 K8s Ingress 或反代默认 1.1,103 完全无效。DevTools Network 里看 Protocol 列必须是 h2/h3。
  2. WAF 或代理吞掉 1xx:某些安全网关不理解 1xx,会直接丢弃。抓包(Wireshark 或 curl --http2 -v)确认客户端收到 103。
  3. CSP 冲突:Chrome 只处理第一个 103,因为多个 103 可能带不一致的 CSP。如果你的 CDN 与源站都想发,会有一个被忽略。选一处即可。
  4. Link 语法错误:忘了尖括号、as 值错、字体 preload 没加 crossorigin,都是静默失败。可以用 DebugBear 的 Early Hints 检查工具验证。
  5. Preload 太多:超过 5 个反而拖慢 LCP。首屏英雄图 + 1 张关键 CSS + 1 个字体 + 1 个 preconnect 已足够。
  6. 响应式图片imagesrcset 在 Link 头里几乎无效,放弃或改用 HTML preload。
  7. 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.finalResponseHeadersStartServer-Timing 头才能准确测量。请把它当作 LCP 优化手段,不是 TTFB 优化手段。

Mateo Silva
关于作者 Mateo Silva

Full-stack performance lead bridging frontend perf with backend latency. Cache invalidation is his love language.