The Resource Loading Problem
Browsers discover resources by parsing HTML. A stylesheet in <head> blocks rendering until it downloads. A font referenced inside that stylesheet is only discovered after the CSS downloads and parses. This waterfall of sequential round trips is one of the largest sources of loading delay.
Resource hints are declarations that let you tell the browser about resources before it discovers them through parsing. The browser can then fetch them in parallel, during idle time, or before the user even clicks a link.
Without hints (waterfall):
HTML → CSS → Font → Paint (3 sequential round trips)
With preload:
HTML + Font fetch in parallel → CSS → Paint (1 round trip saved)
Preload: Fetch Before Discovery
<link rel="preload"> tells the browser: *fetch this resource now, at high priority, I will definitely use it soon*.
<!-- Preload a critical font -->
<link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>
<!-- Preload the LCP hero image -->
<link rel="preload" href="/images/hero.webp" as="image" fetchpriority="high">
<!-- Preload a render-blocking script -->
<link rel="preload" href="/js/app.js" as="script">
The as attribute is required — it tells the browser what kind of resource it is so the correct request headers and caching behavior are applied. Wrong or missing as causes the browser to fetch twice.
| `as` Value | Use Case |
|---|---|
| `script` | JavaScript files |
| `style` | CSS stylesheets |
| `font` | Web fonts (always add `crossorigin`) |
| `image` | Images (especially LCP images) |
| `fetch` | XHR/fetch requests (add `crossorigin`) |
| `document` | Iframe or navigation target |
fetchpriority
The fetchpriority attribute hints at the resource's priority relative to others of the same type:
<!-- Hero image — highest priority -->
<link rel="preload" href="/hero.webp" as="image" fetchpriority="high">
<!-- Below-fold image — lower priority -->
<img src="/thumbnail.webp" fetchpriority="low" loading="lazy">
This is especially useful for LCP (Largest Contentful Paint) optimization: mark the LCP image as fetchpriority="high" and all other images as fetchpriority="low".
Prefetch: Fetch Before the User Navigates
<link rel="prefetch"> tells the browser: *the user might navigate here next — fetch it at low priority during idle time*.
<!-- Prefetch the next article page -->
<link rel="prefetch" href="/articles/next-article/">
<!-- Prefetch a script needed on the next page -->
<link rel="prefetch" href="/js/checkout.js" as="script">
Prefetched resources are stored in the HTTP cache, not the preload cache. If the user navigates to the prefetched URL, the browser serves from cache instantly — eliminating the network round trip entirely.
Speculation Rules API
The modern replacement for prefetch is the Speculation Rules API, which supports both prefetch and full prerender:
<script type="speculationrules">
{
"prefetch": [{
"where": { "href_matches": "/articles/*" },
"eagerness": "moderate"
}],
"prerender": [{
"where": { "href_matches": "/checkout" },
"eagerness": "conservative"
}]
}
</script>
- prefetch: downloads the page HTML in the background
- prerender: renders the full page in a hidden tab — navigation appears instant
- eagerness:
conservative(on hover),moderate(on pointer down),eager(immediately)
Preconnect and DNS-Prefetch
Every connection to a new origin requires DNS lookup + TCP handshake + TLS handshake — often 200-500ms on mobile networks. Resource hints can eliminate this latency.
Preconnect
preconnect performs the full connection setup (DNS + TCP + TLS) without fetching any resource:
<!-- Preconnect to Google Fonts — used within seconds -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Preconnect to your API server -->
<link rel="preconnect" href="https://api.example.com">
Use preconnect only for origins you will definitely use within the first few seconds. Browsers limit the number of preconnected origins, and idle preconnections consume server resources.
DNS-Prefetch
dns-prefetch performs only the DNS lookup — cheaper than preconnect but provides less benefit:
<!-- DNS-prefetch for analytics loaded later -->
<link rel="dns-prefetch" href="https://www.google-analytics.com">
<link rel="dns-prefetch" href="https://cdn.example.com">
Use dns-prefetch as a fallback for browsers that do not support preconnect, or for origins loaded later in the page lifecycle where a full preconnect is overkill.
| Hint | Does | Cost | When to Use |
|---|---|---|---|
| `preconnect` | DNS + TCP + TLS | Medium | Origins used within 3s |
| `dns-prefetch` | DNS only | Low | Origins used later |
Measuring Impact
Chrome DevTools Waterfall
Open the Network panel, record a page load, and look for:
- Resources in the waterfall that start late (discovered late — candidates for preload)
- Long DNS + TLS segments before third-party requests (candidates for preconnect)
- The LCP image starting after CSS download completes (add
fetchpriority="high"preload)
Lighthouse Audits
Lighthouse flags specific resource hint opportunities:
- Preconnect to required origins — lists third-party origins consuming connection time
- Preload Largest Contentful Paint image — detects late-discovered LCP images
- Avoid chaining critical requests — identifies deep discovery chains
# Run Lighthouse from the CLI
npx lighthouse https://example.com --only-audits=uses-rel-preconnect,uses-rel-preload
Key Takeaways
- Use
preloadfor resources on the current page that are discovered late (fonts, LCP images) - Use
prefetch(or Speculation Rules) for next-page resources - Use
preconnectfor origins used within 3 seconds;dns-prefetchfor later origins - Always include
crossoriginon font preloads — fonts are always fetched with CORS - Limit
preconnectto 2-3 origins — too many preconnections waste resources