Resource Hints
Date: 2026-08-17
Telling the browser to start work earlier than it otherwise would — resolving a DNS name, opening a connection, fetching a file. Each one buys time by spending bandwidth and connection slots, so a page that hints at everything has prioritised nothing and is usually slower than one that hints at nothing.
Resource hints are <link rel> values — dns-prefetch, preconnect, preload, prefetch — that tell the browser to start a lookup, connection or download before it would discover the need itself.
The four, by how much they commit
dns-prefetch resolve the hostname ~20–120ms saved
↓ cheapest. no connection opened.
preconnect DNS + TCP handshake + TLS negotiation ~100–300ms saved
↓ a full connection, held open and idle
if unused, you've paid for a socket for nothing
prefetch download a file, low priority, for a a whole request
↓ FUTURE navigation
wasted entirely if they don't navigate there
preload download a file NOW, high priority, for a whole request,
THIS page competing with
the browser still decides when to USE it real traffic
They’re not a ladder of strength — they answer different questions. preconnect is about where you’ll fetch from; preload is about what you’ll fetch; prefetch is about the next page.
The syntax, and the attribute people forget
<!-- warm the connection to a font or image host -->
<link rel="preconnect" href="https://fonts.example.com" crossorigin>
<!-- cheaper, for hosts you'll use but not immediately -->
<link rel="dns-prefetch" href="https://analytics.example.com">
<!-- fetch the hero image now, at high priority -->
<link rel="preload" as="image" href="/hero.avif" type="image/avif">
<!-- fonts ALWAYS need crossorigin, even same-origin -->
<link rel="preload" as="font" type="font/woff2"
href="/fonts/inter.woff2" crossorigin>
<!-- the likely next page, at idle priority -->
<link rel="prefetch" href="/checkout" as="document">Two attributes cause most of the wasted hints:
asis mandatory onpreload. Without it the browser can’t set the right priority or apply the rightAcceptheader, and it will often fetch the file twice — once as the preload and once as the real request. A preload that double-fetches is worse than no preloadcrossoriginon fonts, always — including same-origin fonts. Font requests are made in anonymous CORS mode, so a preload withoutcrossorigindoesn’t match the actual request and downloads separately
Where each earns its place
preconnect — for a third-party origin on the critical path that the browser can’t discover early, most often a font host, an image CDN or a payment iframe.
without preconnect with preconnect
HTML parsed 0ms HTML parsed 0ms
… preconnect starts 5ms
font CSS found 180ms font CSS found 180ms
DNS 30ms connection ready
TCP 40ms (already warm)
TLS 60ms
request 120ms request 120ms
───── ─────
430ms 300ms
Limit it to two or three origins. Each held connection consumes a socket and competes for bandwidth during the most contended moment of the page load, and an unused preconnect is pure cost. The browser closes idle preconnects after around ten seconds, so hinting to something you use late is wasted.
preload — for a critical resource the browser discovers late, which in practice means:
- A font referenced from a CSS file (found only after the CSS downloads and parses)
- The LCP image when it’s set via CSS
background-imagerather than an<img>tag - A critical script loaded by another script
preload is pointless for things the preload scanner already finds early — an <img src> near the top of the HTML, a <script src> in the head. The browser found those before your hint mattered. This is the commonest wasted hint by a wide margin.
prefetch — where the next navigation is genuinely predictable. Product page → basket, basket → checkout, step 1 → step 2. On a mobile connection, prefetching a page nobody visits spends someone’s data allowance for nothing, so keep it to high-confidence paths.
The harm of over-hinting
a page preloading 14 resources
→ all 14 arrive at "high" priority
→ the browser's own prioritisation is overridden
→ the LCP image now competes with 13 other high-priority requests
→ LCP gets WORSE than with no hints at all
Priority is relative. If everything is important, the browser has lost the information it uses to sequence work — and its heuristics are good, so overriding them needs a specific reason.
Symptoms of over-hinting worth checking for:
- Console warnings about a preloaded resource unused within a few seconds — the browser tells you directly
- Double-fetches in the network panel, usually from a missing
asor acrossoriginmismatch - LCP regressing after adding hints, which is the diagnostic that matters
Related controls
Adjacent levers that solve nearby problems, and are often the lighter-touch fix:
fetchpriority="high|low|auto"— reorders a known request without adding one. Try this before reaching forpreload— Priority Hintsloading="lazy"— defers off-screen images. Never on the LCP element, which is a common self-inflicted regression — Lazy Loadingdecoding="async"— moves image decode off the main thread- The
Link:HTTP header — the same hints delivered in the response headers, so they arrive before the HTML body is parsed. Earlier still, and useful when serving from a CDN — CDN Caching
Checking it worked
DevTools → Network, sorted by start time. A successful preload moves the resource earlier in the waterfall; two entries for the same file means a mismatched as or a missing crossorigin — DevTools.
Lighthouse flags both “preload key requests” and unused preloads. The second warning is the more useful one, since it catches hints added once for a resource that no longer exists — which is how a page ends up with four hints and three of them wasted.
The rules
- Measure before and after, in the field. Hints are a reallocation of a fixed bandwidth budget, so a local test on fast fibre shows almost nothing — Field vs Lab Data, Real User Monitoring
- Three or four hints total on a typical page. If you need more, the problem is the number of critical resources, not the hinting — The Critical Rendering Path
- Fix discovery before hinting. Moving a font declaration into the HTML beats preloading it from a CSS file
- Server push is not the answer. HTTP/2 push was the earlier attempt at this problem and has been retired by browsers; preload is the surviving mechanism — HTTP Versions
Where it interacts
- Priority Hints — the finer control, for when the browser knows about a resource and gets its importance wrong
- Render-Blocking Resources — hints reorder work; removing blockers reduces it, and reduction wins
- Font Loading — the single most common legitimate
preload, and where thecrossorigintrap bites - Third-Party Scripts —
preconnectis a cheap partial mitigation for third-party latency you can’t otherwise control