Resource hints: how to preload, preconnect and prefetch what the first screen needs

Resource hints tell the browser about a resource before it discovers it. Here is which of the five types to use for what, a copyable decision table, and a command that lists every hint your page already ships.

Implementation7 min read2501 views
Resource hints: how to preload, preconnect and prefetch what the first screen needs

Resource hints are <link> elements that tell the browser about a resource before the browser would discover it on its own. Five of them matter: dns-prefetch, preconnect, preload, modulepreload, and prefetch. Each buys a different thing, and a hint that buys nothing still costs bytes and attention.

Before you start

You need a page whose first screen depends on something the browser finds late: a font declared inside a stylesheet, an image set in CSS, a script imported by another script. If everything the first screen needs is already in the HTML, hints will not help, because the browser has already found it.

The numbers side of this topic is already measured. The resource hints survey counts how many homepages ship each hint type, and the render-blocking CSS survey covers the stylesheets that delay the first paint. This chapter is the other half: which hint to use, where it goes, and how to check what you already ship.

Resource hints are not an optimization you add everywhere. They are a small number of decisions about what the browser should start early.

The five resource hints, and what each one buys

Pick by what you are trying to move earlier. A hint can open a connection, download a file, or prepare a module, and the cost rises with how much it does.

HintWhat it doesUse for
dns-prefetchResolves the origin's DNS onlyMany third-party origins, cheaply
preconnectDNS plus TCP and TLS to the originThe few origins on the critical path
preloadDownloads one resource at higher priorityFonts, CSS, late-discovered images
modulepreloadFetches, parses and compiles a moduleThe entry module and its graph
prefetchFetches for a future navigation, low priorityThe next page, not this one

MDN describes preconnect as a hint that the user is likely to need resources from an origin, so the browser can start the connection early. The same page adds a clause most people skip: it has no benefit on same-origin requests, because that connection is already open.

Why preload is not the same as the connection hints

The connection hints are suggestions. MDN's guidance and Google's own preload article both draw the line the same way: preconnect and prefetch are executed as the browser sees fit, while preload is mandatory. A preload starts a real fetch at a higher priority, and if the page never uses the file, Chrome logs a console warning about an unused preload roughly three seconds after load. That warning is the cost of getting one wrong.

Preload is worth it for resources the browser discovers late, not for resources already in the HTML. A font declared with @font-face inside a stylesheet is the standard case: the browser cannot request the font until it has downloaded and parsed that stylesheet. The same applies to a background image set in CSS. Preloading these starts the download in parallel with the stylesheet instead of after it.

One attribute is not optional. The as attribute tells the browser what kind of resource it is fetching, which sets the priority, the request headers, and the cache key. A preload without as is fetched twice in some browsers, because the preload and the later real request do not match.

<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/css/critical.css" as="style" />
<link rel="modulepreload" href="/js/app.js" />

Do it in this order

Five steps, each with a check you can run before moving on. The whole pass takes under an hour on a page you already know.

  1. List the late-discovered resources. Fonts, CSS background images, and anything a script imports qualify. Done when every item on the list is something the browser cannot see in the initial HTML.
  2. Decide the origin hints first. For each third-party origin on the critical path, choose preconnect; for the long tail, choose dns-prefetch. Done when no more than three or four origins get a preconnect.
  3. Preload only what the first screen uses. Add one preload per late-discovered critical resource, each with its as value. Done when no preload points at a resource the first screen never renders.
  4. Add modulepreload for the entry module only. It fetches, parses and compiles the module ahead of execution, which is more than preload does. Done when the entry module is covered and the dependencies are left to the browser.
  5. Re-check the page after a week. Hints change the order of requests, and an order that helped one layout can hurt another. Done when the console has no unused-preload warnings and the first screen still renders.

The deliverable: a decision table and a check command

Keep the table. It is the whole method on one screen, and it is the part you will come back to.

SituationHintDo not
One critical third-party originpreconnectPreconnect every origin
Ten third-party origins, none criticaldns-prefetchSpend TLS time on all ten
Font used above the foldpreload with as="font"Omit as or crossorigin
Critical CSS in a separate filepreload with as="style"Preload the whole bundle
App shell loaded by a modulemodulepreloadModulepreload every chunk
The page the user will open nextprefetchPrefetch on a slow connection

The check reads the hints a live page already ships. It is the fastest way to find a preload that points at the wrong thing, or a preconnect to an origin nothing on the page uses.

curl -s -L --compressed -A "Mozilla/5.0" https://example.com/ -o page.html

python3 -c "
import re
html = open('page.html').read()
head = html.split('</head>')[0]
for m in re.finditer(r'<link\b[^>]*>', head):
    t = m.group(0)
    if re.search(r'rel=[\"\\']?[^\"\\'>]*(preload|preconnect|dns-prefetch|prefetch|modulepreload)', t):
        print(re.sub(r'\s+', ' ', t))
"

Read the output against the table. A preconnect to an origin that appears nowhere else on the page is dead weight. A preload with no as is a bug. More than four preconnect lines is usually a page that preconnected everything and prioritised nothing.

What goes wrong, and how you would notice

Three failures cover most broken hint setups. Each one is invisible unless you look at the request order rather than the markup.

  1. Too many preconnects. MDN warns that preconnecting many third-party domains can be counterproductive and that the hint is best kept for the most critical connections. The symptom is a first screen that got no faster after you added them.
  2. A preload the page never uses. The file downloads at high priority and then sits in the cache. Chrome warns about this in the console about three seconds after load; treat the warning as a real defect, not noise.
  3. A preload missing as. The browser cannot match it to the real request, so it fetches the file twice and the second request has the priority you were trying to change.

One boundary before you trust any of this. We did not measure load times, and markup cannot tell you how much a hint is worth on your site. A preconnect only helps if the origin is genuinely used early; a preload only helps if the resource is genuinely on the critical path. The only honest test is a before-and-after on your own page, with the network throttled.

Common questions

How many preconnects are too many?

MDN does not set a number, and it depends on the page. The practical ceiling is the number of origins the first screen truly needs, which is usually three or four. Everything else belongs in dns-prefetch, which is cheap enough to use broadly.

Should I preload every font?

No. Preload the font the first screen renders. Preloading every weight and subset competes with the resources the visitor is actually waiting on, and the unused ones trigger the same console warning as any other unused preload.

Does preload execute the script or apply the stylesheet?

No. It downloads and caches the resource at a higher priority. Execution and application still happen when the page reaches the element that uses it, which is exactly why preloading can move the download earlier without changing when the code runs.

Is prefetch the same as preload for the next page?

Close, but not the same. prefetch is for a future navigation and runs at a lower priority than preload. MDN also notes that browser cache partitioning makes prefetch useless across top-level sites, so it is for pages on your own site.

Do resource hints affect rankings?

There is no ranking claim here, and none of the sources make one. Hints change when a resource is fetched, which is a loading question. What we can say is narrower: a hint on the wrong resource spends bandwidth and buys nothing.

The counts of how many homepages actually ship each hint, and which ones ship none, are in the resource hints survey. If you want to see what a non-rendering client receives from the same page, how QueryWin works is the product side of that question.

Part of the QueryWin handbook · Level 2

Resource hints: how to preload, preconnect and prefetch what the first screen needs