HTTP 103 Early Hints: Cut TTFB and LCP by Preloading During Server Think-Time
HTTP 103 Early Hints lets servers preload critical resources during backend think-time, cutting LCP by 100-400ms. A practical 2026 guide for Cloudflare, Fastly, and Node.js origins.
HTTP 103 Early Hints is an informational response status code that lets a server send Link: rel=preload and rel=preconnect headers to the browser before the final response is ready, so the browser can start fetching critical subresources during the server's think-time. Honestly, in my profiling work across e-commerce and SaaS apps in 2026, Early Hints reliably shaves 100 to 400 ms off LCP whenever there's a slow database query, an authenticated API hop, or a CDN origin-fetch sitting between the request and the first byte of HTML. This guide walks through the trace, the syntax, the CDN configurations, and the pitfalls I keep tripping over.
HTTP 103 Early Hints is an informational interim response that ships Link headers (preload, preconnect, modulepreload) before the final 200 response, parallelizing resource discovery with backend work.
As of May 2026, Early Hints is supported in Chrome 103+, Edge 103+, Firefox 120+, and Safari 18.4+, plus all major CDNs (Cloudflare, Fastly, Akamai, CloudFront).
The biggest wins come on slow-TTFB pages: every millisecond between request and 200 OK becomes a millisecond of useful preloading.
Only preload resources you're 100% certain the final response needs. Wasted Early Hints contend for bandwidth and harm LCP.
Cloudflare auto-generates Early Hints from prior Link response headers; Fastly and Akamai require explicit VCL or EdgeWorker code.
Measure impact with the ttfb attribution from web-vitals v4 and the finalResponseHeadersStart field in the Navigation Timing API.
What is HTTP 103 Early Hints?
HTTP 103 Early Hints, defined in RFC 8297, is an informational response that a server may emit before the definitive response. Unlike the 1xx codes most developers ignore (100 Continue, 101 Switching Protocols), 103 is specifically designed to carry Link headers that the browser treats as if they appeared on the final response. The connection stays open, the server keeps doing its work, and the browser uses the hints to start opening sockets and downloading bytes for assets the page is going to need.
The trace looks like this in Chrome's Network panel: a request is sent, you see a "103 Early Hints" entry, then 80 to 300 ms later the "200 OK" arrives. In between, you see preload requests already firing on the waterfall. That's the entire optimization. The browser stops waiting for the HTML to enumerate its dependencies and starts fetching them in parallel with the server's slow query, auth check, or origin fetch.
The mental model I use when explaining this to teams: Early Hints turns dead server time into client work. If your TTFB is 50 ms, there's nothing to optimize here. If your TTFB is 400 ms because you query Postgres, hit a session store, and assemble a Liquid template, every one of those milliseconds can now be spent opening a TLS connection to your image CDN and downloading the hero image.
How Early Hints actually improves TTFB and LCP
This is the part that confuses people, so let me be precise. Early Hints does not reduce the TTFB number reported by the Navigation Timing API or by Lighthouse (that number is still measured to the first byte of the final 200 response). What Early Hints reduces is the effective TTFB experienced by subresources: the browser starts their requests during the original navigation's TTFB, so their TTFB shifts left on the waterfall.
The metric that moves is LCP, because the LCP image, the LCP web font, or the critical CSS that paints the LCP element all start downloading sooner. In Chrome's web.dev case studies and in my own RUM data, a page with a 350 ms TTFB and a remotely-hosted LCP image typically sees LCP improve by 180 to 280 ms with well-tuned Early Hints. The deeper your origin fetch chain, the bigger the win. I hit this exact pattern on a checkout flow last year, where moving the hero CSS into a 103 dropped LCP from 2.6s to 2.1s on mobile.
There's a second-order win on connection setup. A Link: <https://cdn.example.com>; rel=preconnect; crossorigin hint causes the browser to do DNS + TCP + TLS during the server's think-time, so the first request to the CDN starts already-warm. In HTTP/3 deployments this matters less than in HTTP/2 because connection migration is cheap, but on cold mobile networks a preconnect over Early Hints can save another 80 to 120 ms. If you haven't already, pair this with our deep dive on HTTP/3 and QUIC for web performance for compounding gains.
Browser and CDN support in 2026
Browser support hit the milestone every performance engineer was waiting for in late 2024, when Safari 18.4 landed Early Hints behind no flag. As of May 2026, the support matrix is essentially universal on modern browsers:
Client / CDN
Early Hints support
Notes
Chrome / Edge
Yes (103+)
Shipped August 2022; preload, modulepreload, preconnect all honored.
Firefox
Yes (120+)
Stable since November 2023.
Safari (desktop + iOS)
Yes (18.4+)
Shipped March 2025; matches Chrome behavior.
Cloudflare
Yes (auto-generated)
Smart Early Hints learns from prior Link headers; one toggle.
Fastly
Yes (VCL + Compute)
Use h2.early_hints() in VCL or a Compute@Edge program.
Akamai
Yes (EdgeWorkers)
Configure via EdgeWorker responseProvider.
AWS CloudFront
Yes (Lambda@Edge)
Origin response trigger; return 103 then 200.
Vercel / Netlify
Yes
Vercel emits Early Hints automatically for Next.js App Router preloads.
One sharp edge: HTTP/1.1 cannot reliably carry interim responses through every proxy in the wild, so all major CDNs only serve Early Hints over HTTP/2 or HTTP/3 connections. If your origin still talks HTTP/1.1 to the edge, that part of the chain is fine (the CDN translates), but if you serve directly from an HTTP/1.1 origin, expect inconsistent behavior on older middleboxes. Always test in DevTools with the Network panel filtered to "All".
How do you enable Early Hints on Cloudflare?
Cloudflare is the easiest path because their Early Hints documentation describes a one-click toggle plus an automatic learning mode. Go to Speed → Optimization → Early Hints and enable it. Cloudflare then caches the Link response headers your origin returned on prior requests and replays them as a 103 on subsequent ones, while the origin processes the new request.
The behavior has a catch, though. Cloudflare only emits hints for resources you already declared via Link response headers, not for <link rel="preload"> tags in the HTML body. If your framework declares preloads in HTML (which most do), you need to mirror them as response headers. Here's the pattern I use for a Next.js Edge route:
// app/page.tsx — Next.js App Router on Vercel or Cloudflare Pages
export const runtime = 'edge';
export default function Page() {
return <img src="/hero.avif" alt="" fetchpriority="high" />;
}
// middleware.ts — attach Link headers so the CDN learns them
import { NextResponse } from 'next/server';
export function middleware() {
const res = NextResponse.next();
res.headers.append(
'Link',
'</hero.avif>; rel=preload; as=image; fetchpriority=high'
);
res.headers.append(
'Link',
'<https://fonts.example.com>; rel=preconnect; crossorigin'
);
return res;
}
On the next request after Cloudflare has learned the headers, it will send a 103 with those Link values before your origin even responds. Verify it worked by curling with HTTP/2: curl -I --http2 https://yoursite.com/ and looking for the 103 Early Hints line above the 200 OK.
Enabling Early Hints on Fastly, Akamai, and CloudFront
Fastly exposes Early Hints as a first-class VCL builtin. The simplest version, in vcl_recv or a custom subroutine:
This emits a 103 immediately upon receiving the request, before fetching from origin. Because it's unconditional per route, you have to be confident those routes always need those assets. Wrong guesses waste bandwidth and contend with the actual LCP image. For dynamic origins, use Compute@Edge to inspect headers and emit hints conditionally.
Akamai requires an EdgeWorker. In the responseProvider handler, you call request.respondWith with a 103 stream before fetching origin. AWS CloudFront uses a Lambda@Edge origin-response function that returns a 103 status with appropriate Link headers; CloudFront then forwards the 200 separately. Both setups benefit from MDN's HTTP 103 reference for exact header semantics, particularly that Link can repeat and each value can stack relations like rel=preload; as=image.
One thing that catches teams: CDN-level Early Hints only fires on cache misses. If the page is served from cache, the final 200 arrives so quickly that the browser wouldn't benefit from a 103, and most CDNs skip it. Which is fine, since TTFB is already low on a cache hit.
Origin implementation: Node.js, Nginx, and Caddy
If you control your origin and want to send 103 yourself (skipping CDN-level configuration), here is a Node.js example using the built-in http2 module that I've shipped in production:
import http2 from 'node:http2';
import fs from 'node:fs';
const server = http2.createSecureServer({
key: fs.readFileSync('key.pem'),
cert: fs.readFileSync('cert.pem'),
});
server.on('stream', async (stream, headers) => {
// Send 103 immediately so the browser can start preloading
stream.additionalHeaders({
':status': 103,
'link': [
'</assets/hero.avif>; rel=preload; as=image; fetchpriority=high',
'</assets/app.css>; rel=preload; as=style',
'<https://api.example.com>; rel=preconnect; crossorigin',
].join(', '),
});
// Simulate slow backend work: DB query, auth, template assembly
const html = await renderPage(headers[':path']);
stream.respond({ ':status': 200, 'content-type': 'text/html' });
stream.end(html);
});
server.listen(8443);
Critical detail: additionalHeaders must be called beforerespond, and the server has to be HTTP/2 or HTTP/3. Node's HTTP/1.1 server cannot emit interim responses cleanly because of how clients buffer 1xx codes.
Nginx 1.27+ supports Early Hints natively via the http_early_hints_module. Enable it in your build, then in your server block use early_hints on; combined with add_header Link "..." early_hints; directives. Caddy 2.8+ added Early Hints via the early_hints directive in its caddyfile, which is the simplest stock-config path of any web server I've tried.
What should you preload in Early Hints (and what to skip)?
This is where teams burn the optimization. Early Hints competes for the same bandwidth budget as the eventual HTML and the resources discovered in it. Every byte you preload speculatively that isn't actually needed is a byte that the LCP image can't use. The rule I apply: only preload resources that the next response is guaranteed to reference, and that the browser cannot discover from a streaming preload-scanner pass over the early HTML.
High-value Early Hints targets, in order:
The LCP image, especially if it lives on a different origin from the HTML. Always pair with fetchpriority=high. For more on this combination, see our fetchpriority and Priority Hints LCP guide.
Critical CSS if it's a separate file. If you inline critical CSS already, skip this.
Self-hosted variable fonts with rel=preload; as=font; crossorigin. Web fonts are notorious for arriving late; Early Hints fixes that.
Preconnect to third-party origins the page hits early: analytics endpoints, image CDNs, payment providers. Saves the DNS + TLS handshake.
Module preloads for the entry chunk of your client framework via rel=modulepreload.
Skip these: hero videos (too large), late-loaded analytics scripts (defer them entirely with third-party script auditing), and anything below the fold. Skip CSS that's render-blocking but already inlined. Skip preload for resources that vary by user session unless you generate per-user 103s, which most CDNs can't do.
Measuring the impact with web-vitals and DevTools
Always measure. Without before/after data you can't tell whether your Early Hints configuration helped or hurt, and the difference is the whole point.
In Chrome DevTools, open the Network panel, ensure "Use large request rows" is on, and filter to "Doc". The HTML row should now show two entries when expanded: a 103 and a 200. Scroll the waterfall horizontally and you should see preload requests starting at the timestamp of the 103, not the 200. If they still start at the 200, the hints aren't being honored. Usually that's a CDN configuration miss or an HTTP/1.1 fallback.
For RUM, the web-vitals v4 library exposes TTFB attribution that includes the finalResponseHeadersStart field; the gap between requestStart and finalResponseHeadersStart is your effective server think-time and the maximum theoretical win from Early Hints. Pair this with our RUM with the web-vitals library setup guide to ship the attribution to your analytics backend.
import { onTTFB, onLCP } from 'web-vitals/attribution';
onTTFB(({ value, attribution }) => {
console.log('TTFB:', value);
console.log('Server think-time:',
attribution.responseStart - attribution.requestStart);
});
onLCP(({ value, attribution }) => {
// If LCP element URL appears in your Early Hints, this should be lower
console.log('LCP:', value, 'element:', attribution.element);
});
I segment my dashboards by "had 103" vs "no 103" by tagging the HTML with a server-rendered cookie that records whether Early Hints fired. The delta across a million sessions is the truth; synthetic Lighthouse runs underrate the win because Lighthouse uses a fast simulated origin.
Common pitfalls and how to avoid them
The five failure modes I see most often:
Sending hints for the wrong route. CDN-level configurations often apply a hint set to /*. The LCP image for the homepage is rarely the LCP image for the product page. Scope by route prefix, or use Cloudflare's auto-learning mode.
Forgetting crossorigin on fonts. Web font preloads without crossorigin are silently discarded and the font re-downloads on the actual request. Always: </fonts/inter.woff2>; rel=preload; as=font; type=font/woff2; crossorigin.
Preloading too many resources. I've audited sites with 12+ Early Hints. The TCP slow-start window is small; flooding it means everything arrives slower. Keep Early Hints to 2 to 4 resources, prioritized for the LCP element.
Not invalidating cached hints. If you ship a new hero image URL, Cloudflare's learned hints will keep preloading the old URL until they re-learn. Purge the cache, or hash your asset paths so stale hints become harmless 404s rather than wrong-image preloads.
Origin emits 103 over HTTP/1.1. The client sees a 103, then the connection breaks or the response gets mangled. Always check that your terminating proxy speaks HTTP/2 to the client. If you can't guarantee that, don't emit 103 at the origin. Let the CDN do it.
Frequently Asked Questions
Does HTTP 103 Early Hints reduce TTFB in Lighthouse reports?
No. Lighthouse and the Navigation Timing API measure TTFB to the first byte of the final 200 response, so the 103 doesn't change that number. What improves is LCP, because the resources preloaded by the 103 arrive sooner, and the effective TTFB of those subresources drops. Watch LCP and FCP, not the headline TTFB metric.
Is HTTP 103 supported in Safari?
Yes. Safari shipped Early Hints support in version 18.4 (March 2025) on macOS and iOS, with no flag required. Earlier Safari versions silently ignore the 103 and process only the 200, so emitting Early Hints is safe for older clients. They simply don't benefit.
What is the difference between Early Hints and preload link tags in HTML?
Both tell the browser to fetch a resource early, but they fire at different times. HTML <link rel="preload"> only starts after the browser has received and parsed enough of the HTML to find it, which means after TTFB. Early Hints arrive before the HTML, so preloads kick off during the server's processing time. On slow-TTFB pages, Early Hints wins by the size of the server think-time.
Can Early Hints be used over HTTP/1.1?
The RFC permits it, but in practice no. Many HTTP/1.1 intermediaries don't forward 1xx interim responses correctly, and clients have historically buffered them inconsistently. All major CDNs serve Early Hints only on HTTP/2 and HTTP/3 connections. Make sure your origin-to-CDN and CDN-to-client legs both speak HTTP/2+.
How do I test if Early Hints is actually firing on my site?
Use curl --http2 -I https://yoursite.com/ and look for a HTTP/2 103 line before the HTTP/2 200 line. In Chrome DevTools, open the Network panel, click on the document request, and check the Headers tab. You'll see "Status Code: 103 Early Hints" followed by the final 200. The waterfall view will show preload requests starting before the document response completes.
The Service Worker Static Routing API lets the browser bypass your SW fetch handler for chosen URLs, cutting cold-start latency and LCP. Hands-on guide with 2026 examples, Workbox interop, browser support, and the diagnostics I use to prove the win.
How I shipped a production scheduler.yield fallback for Safari in 2026 using postTask and MessageChannel, kept Chrome's INP wins, and avoided the setTimeout(0) clamp tax.
A field-tested walkthrough of the seven WordPress plugin patterns that push INP past 500ms in 2026, with a defer-and-idle JavaScript fix you can ship today without uninstalling anything.