HAMZA KAREEM / CONCEPTS hamzawi.dev

Lesson 772 min read

Resource Hints (preconnect / preload / prefetch)

Web developmentweb development / performance

The myth

"Page is slow? Preload the important stuff. More preloads = faster."

So you add 12 <link rel="preload"> tags... and LCP gets worse.

Actually

Preload does not create bandwidth. It only reorders the queue.

Your connection moves the same bytes/sec no matter what you annotate. preload says "fetch this now, at high priority, even though the parser hasn't reached it yet." Everything you promote pushes something else down. Preload everything = preload nothing, except now you've also delayed the one file that mattered.

Rule of thumb: preload is a scalpel, 1-3 per page. If you can't name which request yours just beat in the queue, delete it.

The three hints do different jobs

  • preconnect — opens the connection early (DNS + TCP + TLS). Fetches nothing. Saves round trips.
  • preload — fetches a resource for THIS page that the parser would otherwise discover late.
  • prefetch — fetches for the NEXT navigation at lowest priority. Lands in cache, unused now.

Where preload actually earns its place: late discovery

The browser's preload scanner already finds <img> and <script> in your HTML — preloading those buys ~0ms. What it cannot see:

body { font-family: Inter }   ->  @font-face { src: url(/inter.woff2) }

That font is only discovered after CSS downloads AND parses. Two round trips late. That's a real fix:

<link rel="preload" href="/inter.woff2" as="font" type="font/woff2" crossorigin>

Same story for a hero image referenced inside a JS chunk, or a CSS background-image.

The gotchas that bite intermediates

  1. Missing crossorigin on a font preload -> the file is fetched TWICE. Fonts are always fetched in CORS mode, so the non-CORS preload is a different cache key. DevTools tells you: "was preloaded using link preload but not used within a few seconds."
  2. Wrong as value -> same double fetch, plus wrong priority bucket.
  3. preconnect to 8 origins -> each idle socket costs, and you're competing with the browser's own scheduling. Keep it to the 2-3 origins you will definitely hit in the next ~200ms; use dns-prefetch for the maybes.
  4. prefetch on your LCP image does nothing for LCP. Wrong hint, lowest priority.
  5. Preload without also shipping the resource -> you paid full bandwidth for a file you never used.

One number to hold onto

On a ~100ms RTT mobile link, a cold third-party origin costs roughly DNS 50ms + TCP 100ms + TLS 150ms ≈ 300ms before byte one. A single <link rel="preconnect" href="https://cdn.example.com"; crossorigin> removes that from the critical path — usually a bigger win than any preload you'll add.

Prove it in two minutes

DevTools -> Network -> right-click the column headers, enable "Priority". Throttle to Fast 4G, hard reload.

Watch the start time of your font/hero and the Priority of everything above it. Add exactly one preload, reload, compare. If nothing else slid later, you found free time. If something did, you just moved the bottleneck.

Your exercise

Open your app's waterfall and find the resource with the biggest gap between page start and its request start — then check view-source (raw HTML, not the Elements tab). If it's NOT in the raw HTML, it was late-discovered: that's your one legitimate preload candidate.

Add it, measure LCP before and after, and tell me: did anything else get slower?