The Problem: Wasted Time Waiting
Modern web pages depend on many resources: stylesheets, JavaScript bundles, fonts, and API calls. Normally, browsers cannot discover these dependencies until the server sends the full HTML response — but generating that response takes time.
Consider a typical server-rendered page:
Browser sends request
↓
[50ms — waiting for server to query DB and render template]
↓
Browser receives HTML
↓
[Browser parses HTML, discovers CSS/JS/font links]
↓
Browser starts loading those resources
Those 50ms of server think time are completely wasted — the browser is idle while the server works. HTTP 103 Early Hints eliminates this wasted time.
How 103 Early Hints Works
103 is an informational status code (1xx). The server sends a partial response with Link headers before the actual response is ready:
Browser request arrives at server
↓
[Server immediately sends 103]
HTTP/2 103 Early Hints
Link: </styles/main.css>; rel=preload; as=style
Link: </scripts/app.js>; rel=preload; as=script
Link: <https://fonts.googleapis.com>; rel=preconnect
[Browser starts preloading CSS/JS and connecting to font CDN]
[Server continues generating HTML — 50ms of DB queries and rendering]
↓
HTTP/2 200 OK
Content-Type: text/html
<html>...<link rel=stylesheet href=/styles/main.css>...</html>
[Browser uses already-loaded CSS — no additional wait]
The browser treats 103 as a preview: it starts preloading resources immediately, then waits for the real 200 OK. If the actual response contains different Link headers, the browser prioritizes the final response.
Link Header Directives
# Preload — fetch resource early, use immediately when discovered in HTML
Link: </styles/main.css>; rel=preload; as=style
Link: </scripts/critical.js>; rel=preload; as=script
Link: </fonts/geist.woff2>; rel=preload; as=font; crossorigin
# Preconnect — establish TCP+TLS connection early (no data fetched)
Link: <https://cdn.example.com>; rel=preconnect
Link: <https://api.example.com>; rel=preconnect; crossorigin
# DNS prefetch — resolve DNS only (cheaper than preconnect)
Link: <https://analytics.example.com>; rel=dns-prefetch
Performance Impact
Real-world measurements show significant LCP improvements:
- Shopify: 2.5% improvement in LCP for merchants using Early Hints via Cloudflare
- Cloudflare: Reported 30+ ms LCP improvement on median page loads
- Google: Early Hints can eliminate the full server think-time from the critical path
The improvement is most pronounced when:
- Server think time is long (50ms+)
- Critical CSS/JS resources are large
- Third-party font CDNs require a separate connection
# Measure Early Hints impact with WebPageTest:
# https://www.webpagetest.org — look for 'Early Hints' in waterfall
# Chrome DevTools — Performance tab — look for 'Early Hints' before 200 OK
# in the Network waterfall
Server Implementation
Nginx
# Nginx 1.25.1+ supports Early Hints natively
location / {
http2_push_preload on;
# Or directly via add_header before proxy_pass:
add_header Link '</styles/main.css>; rel=preload; as=style' always;
# Send 103 before proxying:
# (requires ngx_http_v2_module + early hints patch in some builds)
proxy_pass http://backend;
}
Cloudflare Early Hints
Cloudflare implements Early Hints at the CDN edge — no server changes needed. Enable in the Speed settings panel. Cloudflare caches the Link headers from previous responses and sends 103 hints for repeat visitors immediately, before the request even reaches your origin:
# Verify Cloudflare Early Hints is active:
curl -I https://example.com 2>&1 | grep -i 'early-hints'
# Check CF-Cache-Status and CF-Ray headers:
curl -sI https://example.com | grep -E '(cf-|server:)'
Python / Django
Python's WSGI protocol does not natively support informational responses. Use ASGI (Starlette/FastAPI/Django ASGI) with HTTP/2:
# Starlette/FastAPI — sending Early Hints (experimental)
from starlette.responses import Response
async def homepage(request):
# Send 103 Early Hints (requires HTTP/2 client)
await request.send_push_promise('/styles/main.css')
# Then generate actual response
content = await render_template('index.html')
return Response(content, media_type='text/html')
Apache
# Apache 2.4.58+ with mod_http2
H2EarlyHints on
Header add Link '</styles/main.css>; rel=preload; as=style'
Caveats and Browser Support
Non-Cacheable by CDNs
The 103 response itself is not cached. CDNs like Cloudflare cache the Link header list from successful responses and use it to generate future 103 responses — the CDN effectively learns which hints to send.
Cross-Origin Restrictions
Preloading cross-origin resources requires crossorigin on the Link header. Without it, fonts and other CORS-required resources fetched via Early Hints may be discarded when used in the actual page.
Browser Support
| Browser | Support | Notes |
|---|---|---|
| Chrome 103+ | Full | Enabled by default |
| Firefox 120+ | Full | Enabled by default |
| Safari 17.2+ | Full | Enabled in late 2023 |
| Edge 103+ | Full | Same as Chrome (Blink) |
Fallback Strategy
Browsers that do not support 103 simply ignore the informational response and wait for the 200 OK — no functional breakage occurs. Early Hints is a pure performance enhancement with zero downside for unsupported clients.