HTMLPublished 6/8/2023

Resource Hints

Break down what dns-prefetch, preconnect, prefetch, preload, and prerender actually do and how much priority each one gets, so you know exactly which one fits which situation instead of throwing all of them onto every page.

Think of the Browser as an Assembly Line

Loading a page is a lot like running an assembly line: the browser parses the HTML, and as it goes, it discovers the CSS, JS, fonts, and images it needs, dispatches downloads for them, and juggles priority — when things get busy, some resources get pushed to the back of the line. What resource hints let you do is cut in line and tell the browser ahead of time: "You're going to need this one soon, go ahead and get it ready" — so the browser doesn't have to wait until it actually parses that line to start working on it.

But cutting in line has a cost: every hint eats into a limited pool of network resources — connections, bandwidth. Cut in line too often and you end up crowding out the things that actually deserved priority. So instead of slapping every hint onto every page, it's worth understanding exactly how far each one prepares things.

dns-prefetch: Look Up the Address Ahead of Time

dns-prefetch does the simplest possible thing: it resolves a domain's DNS ahead of time, converting the domain name into an IP address in advance. It doesn't open a connection or download anything — it just saves you the DNS lookup time whenever that domain actually gets used.

html
<link rel="dns-prefetch" href="https://cdn.example.com" />

It's a good fit for situations where you'll probably connect to a domain soon, but you're not sure yet, or where you might end up touching a bunch of different domains — say, a page full of user comments, each with an avatar hosted on a different image CDN. Since the cost is basically nothing (a single DNS lookup), you can afford to sprinkle in a few more of these without much worry.

preconnect: Build the Entire Connection

preconnect goes a step further than dns-prefetch: it doesn't just resolve DNS, it also opens the TCP connection, and if it's HTTPS, it completes the TLS handshake too. It's the equivalent of dialing the phone, getting through, and confirming who's on the other end — the only thing left is for someone to actually start talking.

html
<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin />

That's exactly why preconnect costs a lot more than dns-prefetch. Opening a connection eats into the browser's limited pool of concurrent connections per origin, and connections have a shelf life — if nothing actually requests anything after one's opened, it just goes to waste. The rule of thumb is to cap preconnect at three or four domains that are genuinely critical and definitely about to be used — a font service, a key API domain. Adding more doesn't help, and can actually slow down other requests.

When fetching a cross-origin resource, remember to add the crossorigin attribute — otherwise the browser may end up opening a separate connection anyway, wasting the work you just did.

prefetch: Probably Needed Soon, Get It Ready Just in Case

prefetch downloads the entire resource and drops it into the cache, but at very low priority — the browser fetches it opportunistically, using idle network capacity, without competing for the current page's bandwidth. It's a good fit for a resource you won't need this time around, but that the user will very likely need on the next page they visit.

html
<link rel="prefetch" href="/next-page-bundle.js" as="script" />

Classic examples: the next page of products on an e-commerce site, the next blog post, or the next step of a multi-step form.

preload: Definitely Needed This Time — Go Get It Now

The biggest difference between preload and prefetch is priority. preload tells the browser "this is a critical resource the current page needs right away," and it gets downloaded immediately at higher priority, competing for the same bandwidth as the normal rendering pipeline.

html
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin />

The classic scenario is a critical font referenced inside CSS, or an image loaded dynamically through JS that the page actually needs right at the start (say, one buried in a background-image that the browser can't spot while parsing HTML). Without a hint, the browser won't discover it needs to download these until it parses the corresponding CSS or JS. preload lets it jump the queue and compete for bandwidth alongside the other critical resources instead of waiting its turn.

There are two common traps with preload:

  1. Forgetting the as attribute: the browser needs to know the resource type to set the right priority and caching behavior. Skip as and the hint is basically dead on arrival.
  2. Preloading something that doesn't actually get used within a few seconds: the console throws a "resource was preloaded but not used" warning, meaning you wasted bandwidth cutting in line for nothing — and possibly slowed down resources that actually needed it. preload is for resources you're certain you'll use but that the browser discovers too late; it's not for resources you're unsure about — that's prefetch's job.

prerender: Just Render the Whole Next Page Already

prerender is the most aggressive hint of the bunch: instead of just downloading resources, it renders the entire page in the background. The moment the user clicks, the page opens almost instantly, because it was already sitting there ready to go. And because it's also the most expensive option — effectively running an invisible tab in parallel — browsers restrict it the most.

One thing worth noting: the old <link rel="prerender"> tag doesn't actually render a full page anymore in current Chrome. Its underlying behavior was quietly swapped out years ago for NoState Prefetch, which only downloads and parses the HTML without running JavaScript — closer to a souped-up prefetch than the instant-open behavior described above. What actually delivers full-page rendering today is the newer Speculation Rules API, which lets you control if and when it kicks in based on probability and conditions, rather than just dropping a <link rel="prerender"> tag and walking away.

html
<script type="speculationrules">
  {
    "prerender": [
      {
        "where": { "href_matches": "/products/*" },
        "eagerness": "moderate"
      }
    ]
  }
</script>

This feature is expensive and browser support is still evolving, so it's best reserved for cases where you're almost certain where the user is headed next — the top result on a search results page, for instance. Most ordinary pages don't need to chase this.

A Quick Comparison Table

HintHow far it goesPriorityBest for
dns-prefetchResolves DNSVery lowDomains you might connect to — fine to use a lot of these
preconnectDNS + TCP + TLSLow, but consumes connection slotsA handful of critical domains you're certain to use soon
prefetchFull download, cachedLow, uses idle bandwidthResources the next page will probably need
preloadFull downloadHigh, competes with the current page for bandwidthResources you're certain to use now, but that the browser discovers too late
prerenderEntire page renderedVery high, and the most expensivePages you're almost certain the user will visit next

The Common Mistake: Hints Are Cutting in Line

Going back to that opening scenario — you added preload and things didn't get faster — the usual culprits are:

  • Preloading a resource that isn't actually needed right away: you've cut in line and hogged bandwidth, pushing genuinely critical resources further back, making everything slower overall.
  • Opening too many preconnect domains: every pre-established connection consumes resources, and opening too many dilutes the browser's connection budget across the board.
  • Forgetting as (for preload) or crossorigin (for preconnect and cross-origin preload): this causes the browser to download the resource twice, or makes the hint useless outright.

At its core, a resource hint works because you know something the browser doesn't yet — what's about to happen next — and you're handing that knowledge over early. The whole thing only pays off if you're actually confident about what's coming, not if you're sprinkling every hint on as insurance.

Wrapping Up

What separates these resource hints really comes down to two axes: how far each one prepares things, and how confident you are that the resource will actually be used. dns-prefetch just looks up an address, preconnect builds the whole connection, prefetch is a speculative early download, preload is a guaranteed-to-be-used early download that competes with the current page for bandwidth, and prerender gets the entire page ready to go. Next time you want to speed up a page, figure out how confident you actually are about the resource you're about to cut in line for, then pick the hint that matches — that'll get you a lot further than slapping every single one onto the page.