Experiments / 08

Resource Hints

Preload, prefetch, preconnect: which one, and when?

  • Advanced
  • 9 min
  • Impact ●●○

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.

Setups
Hints
0 × 100 KB
The page and network
120 ms
10 Mbps
300 ms
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
Network
  • Not requested yet
  • DNS
  • TCP
  • TLS
  • Waiting
  • Download
2.23 s

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

  1. Find the late discoveries. With no hints, note when the hero, the font and the API request begin. Each waits for something earlier.
  2. 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.
  3. Preconnect the API origin. The DNS, TCP and TLS bars slide to the left, before the script even asks. Try dns-prefetch too and compare how much each saves.
  4. Add modulepreload. The staircase of dependent module files becomes one parallel block, and the API call can start much sooner.
  5. Raise the server time to 800 ms and turn on Early Hints. The hints go out while the server is still working.
  6. Preload everything. Slide “Unneeded preloads” up and watch the hero get later. Then set the hero’s fetchpriority to 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 as type, for use on this page. It needs an as value (style, script, font, image), and fonts need crossorigin. The browser warns if a preloaded file isn’t used soon after load. For responsive images use imagesrcset and imagesizes.
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
high or low on 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 Link headers 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) or modulepreload for a chain.
  • It’s a different origin you’ll use soon: preconnect if certain, dns-prefetch if likely.
  • Several things compete and one matters most: fetchpriority on that one. Often the hero.
  • The server is slow to reply: 103 Early Hints so the hints don’t have to wait for it.
  • You know where visitors usually go next: prefetch it, 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

  1. In DevTools Network, add the Priority and Initiator columns. A late request with a long initiator chain is a preload candidate.
  2. Read the Console: Chrome warns when something was preloaded but not used, which means you preloaded the wrong thing or too early.
  3. 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.
  4. 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. preconnect starts them when hints are seen; dns-prefetch only 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.