Experiments / 08
Resource Hints
Preload, prefetch, preconnect: which one, and when?
The problem
A browser can only request what it knows about. It finds out by reading the HTML, then the CSS, then running the JavaScript, and each of those discoveries is a step on a chain. The last file on the chain might not be requested until a second after the page started.
Resource hints let you tell the browser what you already know. That the font will be needed. That an API lives on another origin. That the next page is likely. They don’t make the network faster. They change when requests start, and how they rank against each other.
Hints, applied one at a time
Begin on “No hints”. Add one hint at a time, checking which row of the waterfall moves, then try “Preload everything” to see what too much looks like.
- Page ready
- 2.17 sno hints: 2.17 s
- Hero image
- 1.59 s1.59 s
- Font
- 1.28 s1.28 s
- API data
- 2.17 s2.17 s
- Not requested yet
- DNS
- TCP
- TLS
- Waiting
- Download
Page ready at 2.17 s (the same as with no hints).
Hints are seen when the HTML arrives, or at the 103 response with Early Hints. Priorities are modelled as bandwidth weights.
Things to try
- Find the late discoveries. With no hints, note when the hero, the font and the API request begin. Each waits for something earlier.
- Preload the hero. It now starts with the HTML. Then tick “Hero is an <img> in the HTML”: the preload does nothing, because the browser already found it. A hint helps only where discovery is late.
- Preconnect the API origin. The DNS, TCP and TLS bars slide to the left, before the script even asks. Try
dns-prefetchtoo and compare how much each saves. - Add
modulepreload. The staircase of dependent module files becomes one parallel block, and the API call can start much sooner. - Raise the server time to 800 ms and turn on Early Hints. The hints go out while the server is still working.
- Preload everything. Slide “Unneeded preloads” up and watch the hero get later. Then set the hero’s
fetchpriorityto high and see how much that fixes.
The hints
- dns-prefetch
- Resolve the hostname early. Saves one round trip and costs almost nothing. A good default for third-party origins you’ll use a bit later.
- preconnect
- Do the whole handshake early: DNS, TCP and TLS, which is three round trips on a fresh connection. For the one or two origins you’re certain to use. Chrome closes a preconnected connection that isn’t used within about 10 seconds, and each one costs the browser and the server something, so don’t preconnect to everything.
- preload
-
Fetch this file now, at a priority that depends on its
astype, for use on this page. It needs anasvalue (style, script, font, image), and fonts needcrossorigin. The browser warns if a preloaded file isn’t used soon after load. For responsive images useimagesrcsetandimagesizes. - modulepreload
- Like preload, but for JavaScript modules: it also fetches and parses the module, so a chain of imports doesn’t have to be discovered one file at a time.
- prefetch
- Fetch something for the next navigation, at the lowest priority, when the browser is idle. It’s a bet: it pays off if the visitor goes there, and wastes data if they don’t. Be careful on metered connections.
- fetchpriority
-
highorlowon an individual request, as a hint about how it should rank against the others. It reorders requests. It does not create bandwidth, so it works best when you raise one thing. - 103 Early Hints
-
The server sends an early response with
Linkheaders while it is still producing the page. The browser can then start the preloads and preconnects before the HTML arrives. It’s most useful when the server takes a while to respond. Support is growing, so check it before relying on it.
<link rel="preconnect" href="https://api.example.com">
<link rel="dns-prefetch" href="https://cdn.example.net">
<link rel="preload" href="/hero.avif" as="image" fetchpriority="high">
<link rel="preload" href="/fonts/plex-400.woff2" as="font" type="font/woff2" crossorigin>
<link rel="modulepreload" href="/js/dep1.js">
Common misconceptions
“Preload makes everything faster.”
Preload makes that one request start sooner and rank higher. Everything else on the page now has more to compete with for the same bandwidth. The widget’s “preload everything” setup finishes later than a modest one, and even later than having no hints for some requests.
“I should preconnect to every third party.”
Preconnect to the origins on your critical path. A connection that goes unused is wasted
work, and Chrome closes an unused preconnect after about 10 seconds. dns-prefetch is the cheap choice for
everything else.
“A hint is a command.”
They’re hints. The browser may ignore them, for example on a slow or metered connection, in data-saver mode, or when it has already done what you asked.
When to reach for which
- The browser finds it late, and it’s on the critical path:
preload(a CSS background hero, a font, a late script) ormodulepreloadfor a chain. - It’s a different origin you’ll use soon:
preconnectif certain,dns-prefetchif likely. - Several things compete and one matters most:
fetchpriorityon that one. Often the hero. - The server is slow to reply:
103 Early Hintsso the hints don’t have to wait for it. - You know where visitors usually go next:
prefetchit, or go further with prerendering (Experiment 11). - Better than any hint: make the browser discover it early without one, such as an
<img>in the HTML instead of a CSS background. The widget’s “already in the HTML” case shows why.
See it on a real page
- In DevTools Network, add the Priority and Initiator columns. A late request with a long initiator chain is a preload candidate.
- Read the Console: Chrome warns when something was preloaded but not used, which means you preloaded the wrong thing or too early.
- In the Performance panel, find the request your LCP depends on. The gap between the HTML arriving and that request starting is the discovery delay a hint could remove.
- Lighthouse’s “Preload key requests” and “Preconnect to required origins” audits point at the same gaps.
Model assumptions
What this simulation simplifies
- One page: 40 KB CSS, a three-file module chain (60/30/30 KB), a 300 KB hero, a 60 KB font, a 40 KB third-party script, and a 20 KB API call that happens after the modules run.
- The hero is found after the CSS is parsed (or at once if it’s an
<img>). The font is found after style and layout. Each module is found after the previous one arrives. - A fresh origin costs three round trips.
preconnectstarts them when hints are seen;dns-prefetchonly the first. - Hints are seen when the HTML arrives, or about 20 ms after the request with Early Hints. Real Early Hints support varies.
- Priority is a bandwidth weight: CSS 4, scripts 2, fonts 3, images 1 (4 with
fetchpriority="high"), analytics 0.6 (0.15 low), prefetch 0.1. Real browsers use their own heuristics. - The next navigation is a 20 KB document plus a 100 KB script on a warm connection, with the click shortly after the page is ready.