The four classic hints, where fetchpriority and the Speculation Rules API fit, and how
to defend each choice on a whiteboard at Staff/Lead level.
Every resource has a discovery moment — when the browser learns it needs it (usually mid-parse, or after JS runs). A resource hint moves work earlier than discovery, spending bandwidth/CPU now on a bet that you'll need the resource soon.
That framing gives you the entire trade-off in one sentence — say this in the room:
“A hint is a bet on what you'll need next. Bet right → faster. Bet wrong → wasted bandwidth. So only bet on sure things.”
There are two distinct jobs, and conflating them is the classic mistake:
preconnect,
dns-prefetch.preload, modulepreload (this
page), prefetch / Speculation Rules (the next page).Resolves a cross-origin hostname ahead of time. Tiny cost, wide support. Use it as the fallback for many third-party origins you might touch.
Opens a full warm connection. Powerful but each one holds a socket — reserve for the 2–4 most critical cross-origins (e.g. your image CDN, fonts host).
Forces an early, high-ish priority fetch for something the parser discovers late — a font in CSS, a hero image, a critical async chunk.
Low-priority fetch into cache for a likely future navigation. Now largely superseded by the Speculation Rules API (§4).
| Hint | What it does | Priority | For |
|---|---|---|---|
dns-prefetch |
DNS resolution only | lowest | this page, future origins |
preconnect |
DNS + TCP + TLS handshake | n/a (connection) | this page, critical origins |
preload |
Fetch a known resource now | High / as-typed | this page |
modulepreload |
Fetch + parse an ES module graph | High | this page (JS) |
prefetch |
Fetch for future use, cache it | Lowest (idle) | next page |
| Speculation Rules | Prefetch or full prerender of a URL | tunable “eagerness” | next page |
Sources: web.dev — resource hints · MDN — Speculative loading
<!-- Connection-level: warm the pipe to your image CDN -->
<link rel="preconnect" href="https://images.example.net">
<link rel="dns-prefetch" href="https://images.example.net"> <!-- fallback for old browsers -->
<!-- Content-level: hero image for this page, made truly high priority -->
<link rel="preload" as="image" href="/hero.avif" fetchpriority="high">
<!-- Font: MUST have crossorigin, even same-origin, or it downloads TWICE -->
<link rel="preload" as="font" type="font/woff2"
href="/inter.woff2" crossorigin>
as is mandatory on preload. Omit it and the browser can't set the right
priority or match the later request, so it fetches the resource a second time. as is what tells it
“this is a font / image / script.”
crossorigin even same-origin. Fonts are fetched in CORS mode (anonymous).
A preload without crossorigin uses a different mode, the cache keys don't match, and you download the
font twice — the single most cited preload bug. [web.dev]
fetchpriority — the modern leverPreloading an image does not make it high priority — images default to low. Add
fetchpriority="high" to your LCP hero, and fetchpriority="low" to below-the-fold or
non-critical fetches to yield bandwidth. This is the 2024+ answer to “how do you make the LCP image win the
race.”
“Preload says find this early. fetchpriority says and win the race. For the hero
image you need both — preload alone still leaves it at low priority.”
high?Priority is relative — it only helps by creating contrast. Mark five preloads
fetchpriority="high" and you've flattened the hierarchy right back out:
<head> wins, which may not be the one that matters.high to the single LCP
resource and push competitors (a gallery, below-the-fold images) to low to clear its lane. The win
comes from the contrast, not the label. Exception: a couple of high hints on genuinely
non-competing critical resources (LCP image and a text-blocking font) is fine — they're not racing
for the same finish line.
“fetchpriority is a ranking, not a megaphone. If everything's high, nothing is. One
high for the LCP, low for its competitors to clear the lane.”
preload as="script" vs modulepreloadFor an ES module (<script type="module">), there are two ways to fetch early — and they get
the module to very different stages of the pipeline:
preload as="script" |
modulepreload |
|
|---|---|---|
| Does | downloads → HTTP cache (raw bytes) | downloads + parses + compiles → module map, ready to run |
| Dependency graph | just that one file | can fetch the import graph too |
| For | classic scripts, anything | ES modules specifically |
ES modules load sequentially by nature: the browser can't discover b.js until it has fetched
and parsed a.js and seen the import. modulepreload lets it walk the static
imports and fetch them in parallel — flattening that waterfall. The parse/compile is done ahead of time
too, so at execution it's near-free.
modulepreload handles it; using
preload as="script" for a module without matching crossorigin gives the
double-download bug (same class as the font trap). (2) Auto-fetching dependencies is a browser
optimization, not a guarantee — to be safe you modulepreload each module (bundlers like Vite emit
these for you). And support: modulepreload is Chromium + Firefox, Safari only since 17.5
(2024) — older Safari ignores it and falls back to preload as="script".
“preload gets you the bytes; modulepreload gets you the parsed module —
and flattens the import waterfall. For ES modules, reach for modulepreload.”
Sources: web.dev — Preload modules · MDN — rel=modulepreload · caniuse — support
Saying “use <link rel=prefetch> for the next page” is a 2019 answer. The current platform
direction is the Speculation Rules API, which supersedes <link rel=prerender> and
improves on prefetch.
<script type="speculationrules">
{
"prerender": [{
"where": { "href_matches": "/hotel/*" },
"eagerness": "moderate"
}]
}
</script>
conservative → eager) lets you trade resource waste for speed —
e.g. prerender on hover vs on viewport.visibilitychange / Page Visibility API.Sources: MDN — Speculation Rules API · Chrome — Prerender pages
Tie it to their product — a hotel search/listing page:
fetchpriority=high on the first above-the-fold hotel photo (the likely LCP
element).Concept: hints move network work before discovery. Trade-off: every hint competes for the same bandwidth, so over-hinting slows the critical path. Anchor: “We preconnected to 8 origins ‘to be safe’ and regressed LCP; cutting to the 3 that mattered recovered it.” Impact: p75 LCP is a ranking + conversion lever on an SEO-critical site. Invite: “I'd prerender more aggressively on desktop where bandwidth is cheap, conservatively on mobile data.”
Pick an answer; you get instant feedback. These mimic the push-back style of the round.
1. You preload a same-origin WOFF2 font with
<link rel="preload" as="font" type="font/woff2" href="/inter.woff2">. What happens?
2. Your LCP is a hero image. You add <link rel="preload" as="image"> but LCP
barely improves. Best next move?
3. Interviewer: “We have 10 third-party origins the page might call. Should we
preconnect them all?” Strongest Lead answer?
They're probing whether you understand cost, not just the API.
4. You want near-instant navigation to the top hotel result on hover. Most current technique?
5. Which is the single best one-sentence framing of the resource-hint trade-off?
6. A teammate marks all six above-the-fold images fetchpriority="high" to “make the
page fast.” What's the most likely result?
Classic over-promotion trap — they're testing whether you know priority is relative.
7. You're early-loading an ES module entry point (<script type="module">) that
imports several others. Best hint?
0 / 7 answered
<head> of a hotel search results page, and defend why each one is worth the bandwidth.” Time
yourself to 90 seconds.