Resource Hints — preload, prefetch, preconnect, dns-prefetch, modulepreload
TL;DR
Resource hints tell the browser to start work earlier than its default discovery would. Five common ones, each at a different layer:
| Hint | Tells the browser to | When |
|---|---|---|
dns-prefetch |
resolve the DNS for an origin | early, very cheap |
preconnect |
open the TCP+TLS to an origin (warm the connection) | when you know a request to that origin is imminent |
preload |
fetch a specific resource ASAP at high priority | critical CSS, fonts, LCP image, key JS |
prefetch |
fetch a resource for future navigation, low priority | next-page resources during idle |
modulepreload |
preload + parse an ESM module and its dependency graph | critical JS modules |
Used surgically, they shave hundreds of milliseconds off LCP. Used liberally, they fight your own bandwidth and hurt the things that actually matter. Measure.
Interview Q&A
Q: When use dns-prefetch?
A: When you’ll connect to a third-party origin you know about up-front but haven’t connected to yet.
<link rel="dns-prefetch" href="https://api.example.com" />
<link rel="dns-prefetch" href="https://cdn.example.com" />
DNS resolution is cheap (typically 20-100ms) but a real serial step. Doing it early in parallel with HTML parsing saves that time when the request fires.
Use for: known third-party origins (analytics, ad servers, fonts.googleapis.com when used, CDN domains).
Skip for: the origin already serving the HTML (you’re already connected) or origins you’ll preconnect to (preconnect supersedes).
Q: preconnect vs dns-prefetch?
A: preconnect does DNS + TCP + TLS ahead of time — opens a warm connection.
<link rel="preconnect" href="https://cdn.example.com" />
<link rel="preconnect" href="https://api.example.com" crossorigin />
- ~250-1000ms saved per connection on fresh visits (cold cache).
- More expensive than
dns-prefetch— opens a real connection. Limit to 3-4 critical origins. crossoriginattribute needed if you’ll fetch with credentials (or for fonts).
Rule: preconnect to ~3 critical origins; dns-prefetch to others. Don’t preconnect to a dozen.
Q: When use preload?
A: Force the browser to fetch a specific resource immediately at high priority, before normal discovery. Use for resources that:
- Are critical to LCP but discovered late by the preload scanner.
- Are declared in CSS (the scanner can’t see them until CSS parses).
Examples:
<!-- The LCP hero image, before <picture>'s srcset selection -->
<link rel="preload" as="image" href="hero.avif" imagesrcset="hero-400.avif 400w, hero-800.avif 800w" imagesizes="100vw" />
<!-- Critical font, referenced in CSS -->
<link rel="preload" as="font" type="font/woff2" href="/fonts/Inter.woff2" crossorigin />
<!-- A JS module loaded by another module -->
<link rel="modulepreload" href="/app/heavy.js" />
Critical: as attribute must match the resource type, otherwise the browser may refetch.
Don’t over-preload — every preloaded resource competes for bandwidth with the others. Preloading a 500 KB image plus three fonts will slow LCP, not improve it.
Q: modulepreload vs preload as="script"?
A: modulepreload is the ESM-aware version — it preloads and the browser parses the module’s dependency graph and preloads those too. Critical for ESM-heavy apps where one chunk imports many others.
<link rel="modulepreload" href="/chunks/dashboard.js" />
For non-module scripts, <link rel="preload" as="script" href="...">. For ESM, always modulepreload.
Modern bundlers (Vite) emit modulepreload links automatically for entry chunks and their imports.
Q: When use prefetch?
A: Low-priority fetch of resources for future navigations.
<link rel="prefetch" href="/dashboard/index.html" as="document" />
<link rel="prefetch" href="/chunks/dashboard.[hash].js" as="script" />
Browser fetches when idle, stores in HTTP cache. User clicks the link → loads from cache, near-instant.
Use cases:
- Next likely route (Next.js Link prefetches viewport-visible routes by default).
- Code-split chunks the user might need.
- Hover-intent prefetch on nav items (
onMouseEnter).
Don’t prefetch everything. On a cellular connection, prefetching a heavy chunk the user never clicks wastes their data plan. Be selective. Next.js does this only for in-viewport <Link>s on Wi-Fi-like connections.
Q: Priority Hints — fetchpriority attribute.
A: A separate but related mechanism on <img>, <link>, <script>, <iframe>:
<img src="hero.avif" fetchpriority="high" alt="..." />
<script src="defer-me.js" defer fetchpriority="low"></script>
Values: high, low, auto (default).
Common uses:
fetchpriority="high"on the LCP image — bumps it above other images and most scripts.fetchpriority="low"on lazy/below-fold scripts or images you specifically want deprioritized.
Newer (Chrome 102+, Safari 17+) but well supported. The cheapest single LCP win when the LCP image is loading too slowly.
Q: Concrete LCP fix — image discovered late.
A: Page LCP is a hero image inside a React component, not in the initial HTML. The preload scanner can’t see it. Two fixes:
- Server-render the
<img>into the HTML so the preload scanner finds it normally. - Add a preload tag in
<head>(server-rendered or static):<link rel="preload" as="image" href="/hero.avif" fetchpriority="high" />
Both move the image’s start time from “after JS executes” to “immediately.”
Q: Speculation Rules API — the newer thing.
A: A Chrome-led API for prefetch and prerender of full pages.
<script type="speculationrules">
{
"prerender": [
{ "urls": ["/dashboard"], "eagerness": "moderate" }
]
}
</script>
prerender actually pre-renders the page in a hidden frame; click → swap. The cost: extra work for pages the user may not visit; only do for high-confidence next steps.
Adoption is growing; Safari/Firefox catching up. The pattern is the future of “instant nav” but Next.js’ viewport-visibility prefetch still covers most needs.
Q: How does the preload scanner work and when does it fail?
A: When the HTML parser hits a <script>, it pauses parsing to run the script (unless async/defer). To keep the network busy, the browser also runs a preload scanner in parallel — a tokenizer that just finds resources to fetch (link/script/img/source URLs) and queues them.
The scanner finds:
<link>and<script>tags in HTML.<img src>(andsrcset).<source>inside<picture>and<video>.
The scanner does not find:
- Resources loaded by JavaScript (
fetch(...), dynamicimport()). - CSS background images (
@importURLs,background-imageproperties). - Images set by client-side rendered components.
For these, resource hints in <head> make up the difference.
Gotchas / edge cases
- Wrong
asvalue onpreload→ browser refetches the resource.as="font"for fonts,as="image"for images, etc. - Missing
crossoriginon font preload → browser may not actually reuse the preloaded response. - Preloading too many things competes with each other; LCP gets worse, not better. 3-5 critical preloads is a sane max.
preloadof a resource you never use is a 100% waste — warned in DevTools console.prefetchover cellular can burn user data.connection-awarehints (vianavigator.connection) can gate.dns-prefetchis a hint, not a guarantee — browsers may ignore on slow connections.modulepreloadnot supported on older Safari — fall back topreload as="script" crossoriginfor those.<link rel="preconnect">to a server that’s down still costs the round trip — preconnect only to origins you’re sure about.
What a senior is expected to say
- “Resource hints are surgical, not free.
dns-prefetchfor cheap DNS warmup,preconnectfor 3-5 critical origins,preloadfor things the preload scanner can’t find (LCP image, CSS-referenced fonts),prefetchfor next-likely navigation,modulepreloadfor ESM critical chunks.” - “
fetchpriority='high'on the LCP image is the cheapest single LCP improvement when the image is loading slow.” - “Preload scanner finds tags in HTML but not resources loaded by JS or CSS. For those, hints in
<head>are the bridge.” - “Over-preloading hurts — every preload competes for bandwidth. Limit to truly critical resources, measure with Lighthouse.”
- “Speculation Rules API +
prerenderis the future for instant nav, but selectiveprefetchcovers most needs today.”
Cross-references
- LCP measurement and fix flow: 01_core_web_vitals.md
- Image and font specifics (where hints apply): 07_image_and_font_optimization.md
- Lazy loading + prefetch on hover: 05_lazy_loading.md
Further reading
- W3C — Resource Hints: https://www.w3.org/TR/resource-hints/
- web.dev — Preload critical assets: https://web.dev/articles/preload-critical-assets
- web.dev — Priority Hints (
fetchpriority): https://web.dev/articles/fetch-priority - Chrome DevRel — Speculation Rules: https://developer.chrome.com/docs/web-platform/prerender-pages