Preload, Prefetch, Preconnect: When to Use Each | OpsBlu Docs

Preload, Prefetch, Preconnect: When to Use Each

How to use dns-prefetch, preconnect, prefetch, preload, and modulepreload correctly. Covers decision framework, performance impact, common...

Resource Hints Overview

Resource hints tell the browser to start work early — resolving DNS, establishing connections, or fetching resources — before it discovers them naturally through HTML parsing. Used correctly, they eliminate latency. Used incorrectly, they waste bandwidth and CPU, and can actually hurt performance.

Hint What It Does Cost When to Use
dns-prefetch Resolves DNS only Minimal (~1 KB UDP packet) Any third-party domain you'll need
preconnect DNS + TCP + TLS handshake Low (~3–5 KB, holds a connection open) Critical third-party domains (max 2–4)
prefetch Fetches entire resource at low priority Medium–High (full resource download) Resources needed on the next page
preload Fetches resource at high priority for current page High (full download, blocks load event if unused) Late-discovered critical resources
modulepreload Like preload but for ES modules (also parses/compiles) High Critical JS modules in <script type="module">

Decision Framework

Is the resource on a third-party domain?
├─ YES → Will you use it in the next few seconds?
│   ├─ YES → <link rel="preconnect">
│   └─ NO  → <link rel="dns-prefetch">
└─ NO (same origin)

Is the resource needed on THIS page?
├─ YES → Is it discovered late by the browser?
│   ├─ YES → <link rel="preload">
│   │        (fonts loaded via CSS, images in CSS backgrounds,
│   │         JS loaded dynamically, data fetched by JS)
│   └─ NO  → Don't add a hint (browser finds it naturally)
└─ NO → Is it needed on a LIKELY NEXT page?
    ├─ YES → <link rel="prefetch">
    └─ NO  → Don't add a hint

dns-prefetch

Resolves DNS for a domain without establishing a connection. Costs almost nothing and has wide browser support (including older browsers that don't support preconnect).

<link rel="dns-prefetch" href="https://www.googletagmanager.com">
<link rel="dns-prefetch" href="https://fonts.googleapis.com">
<link rel="dns-prefetch" href="https://connect.facebook.net">

When to use: For any third-party domain your page will request. There's no practical limit — dns-prefetch is cheap enough to apply broadly.

Common mistake: Using dns-prefetch for your own domain. The browser already resolved your domain to load the page — dns-prefetch is only useful for other domains.

preconnect

Performs DNS resolution + TCP connection + TLS handshake with a remote server before the browser discovers it needs to fetch from that domain. Saves 100–300ms per connection on typical networks.

<!-- Font files come from gstatic, not googleapis -->
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preconnect" href="https://cdn.example.com">

When to use: For the 2–4 most critical third-party domains. Each preconnect holds an open connection, consuming browser and server resources. More than 4–6 preconnects can actually hurt performance by competing for limited connection slots.

The crossorigin attribute matters:

<!-- For resources fetched with CORS (fonts, fetch API, ES modules): -->
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>

<!-- For resources fetched WITHOUT CORS (scripts, images): -->
<link rel="preconnect" href="https://cdn.example.com">

<!-- WRONG: missing crossorigin for fonts causes a SECOND connection -->
<link rel="preconnect" href="https://fonts.gstatic.com">
<!-- Browser makes non-CORS connection, then makes a SEPARATE CORS connection for the font -->

Best practice — pair with dns-prefetch as fallback:

<!-- preconnect for modern browsers, dns-prefetch for older ones -->
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="dns-prefetch" href="https://fonts.gstatic.com">

preload

Tells the browser to fetch a specific resource immediately at high priority because you know it's needed for the current page but the browser won't discover it until later.

<!-- Font file: browser won't discover it until CSS is parsed -->
<link rel="preload" href="/fonts/brand.woff2" as="font" type="font/woff2" crossorigin>

<!-- Hero image: browser won't discover it until CSS background is applied -->
<link rel="preload" href="/images/hero.webp" as="image">

<!-- Critical CSS loaded via JS: -->
<link rel="preload" href="/styles/above-fold.css" as="style">

When to use: Only for resources that are both critical AND late-discovered. "Late-discovered" means the browser can't find them by scanning the HTML — they're referenced inside CSS files, loaded dynamically by JavaScript, or served from CSS background-image.

Common mistakes:

<!-- WRONG: preloading a resource that's already in the HTML -->
<!-- The browser already found this <script> tag naturally -->
<link rel="preload" href="/app.js" as="script">
<script src="/app.js"></script>
<!-- This is redundant and wastes a preload slot -->

<!-- WRONG: preloading without using within 3 seconds -->
<link rel="preload" href="/fonts/decorative.woff2" as="font" type="font/woff2" crossorigin>
<!-- If this font isn't used above the fold, Chrome will warn:
     "The resource was preloaded using link preload but not used within
     a few seconds from the window's load event." -->

<!-- WRONG: missing 'as' attribute -->
<link rel="preload" href="/data.json">
<!-- Without 'as', the browser can't set correct priority or Content-Type -->

<!-- WRONG: missing crossorigin for fonts -->
<link rel="preload" href="/fonts/brand.woff2" as="font" type="font/woff2">
<!-- Fonts are always fetched with CORS; without crossorigin, the preloaded
     response can't be used and the font is fetched again -->

The as attribute reference:

Resource Type as Value
CSS stylesheet style
JavaScript script
Font font
Image image
Video video
Audio audio
JSON/data fetch

prefetch

Fetches a resource at idle priority for use on a future navigation. The browser downloads it when the network and CPU are idle, storing it in the HTTP cache for the next page load.

<!-- User is on the product listing page; prefetch the product detail page -->
<link rel="prefetch" href="/products/popular-item.html">

<!-- Prefetch next-page JavaScript bundle -->
<link rel="prefetch" href="/js/checkout.js">

When to use: When you can predict the user's next navigation with reasonable confidence. Product listing → product detail. Blog index → first article. Multi-step form step 1 → step 2.

When NOT to use:

  • Don't prefetch 20 possible next pages. Pick the 1–3 most likely.
  • Don't prefetch on metered connections (mobile data). Use the Network Information API to check:
if (navigator.connection && !navigator.connection.saveData) {
  const link = document.createElement('link');
  link.rel = 'prefetch';
  link.href = '/likely-next-page.html';
  document.head.appendChild(link);
}

Diagnosing Resource Hint Issues

Check for Unused Preloads

Chrome Console will show a warning ~3 seconds after load if a preloaded resource wasn't used:

"The resource https://example.com/font.woff2 was preloaded using link preload but not used within a few seconds from the window's load event."

Verify Hints Are Working

// Check all resource hints in the document
document.querySelectorAll('link[rel]').forEach(link => {
  if (['preconnect', 'dns-prefetch', 'preload', 'prefetch', 'modulepreload'].includes(link.rel)) {
    console.log(`${link.rel}: ${link.href} ${link.as || ''} ${link.crossOrigin || ''}`);
  }
});

Measure Connection Savings

// Compare resources with and without preconnect
// Resources to preconnected origins should show 0ms connect time
performance.getEntriesByType('resource').forEach(r => {
  const domain = new URL(r.name).hostname;
  const connect = r.connectEnd - r.connectStart;
  const dns = r.domainLookupEnd - r.domainLookupStart;
  if (connect > 0 || dns > 0) {
    console.log(`${domain}: DNS ${Math.round(dns)}ms, Connect ${Math.round(connect)}ms`);
  }
});
// If a preconnected domain still shows >0ms connect time, the hint isn't working
// (check crossorigin mismatch or too many preconnects)

Lighthouse Audit

Lighthouse flags:

  • "Preconnect to required origins" — suggests preconnect for third-party domains that add latency.
  • "Preload key requests" — suggests preload for late-discovered critical resources.
  • "Avoid unnecessary preloads" — flags preloaded resources that weren't used.

Practical Examples

Google Fonts Optimization

<!-- 1. Preconnect to the font CSS + font file domains -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>

<!-- 2. Load the CSS with display=swap (prevents FOIT) -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">

Without the preconnects, the browser must: parse HTML → find stylesheet link → DNS + connect to googleapis.com → download CSS → find font URLs → DNS + connect to gstatic.com → download fonts. The preconnects eliminate the connection steps.

Analytics / Tag Manager

<!-- GTM container needs preconnect (it's critical and loads early) -->
<link rel="preconnect" href="https://www.googletagmanager.com">

<!-- Analytics endpoints: dns-prefetch is sufficient (they fire later) -->
<link rel="dns-prefetch" href="https://www.google-analytics.com">
<link rel="dns-prefetch" href="https://connect.facebook.net">
<link rel="dns-prefetch" href="https://analytics.tiktok.com">

SPA Code Splitting

// Prefetch route chunks that the user is likely to navigate to
// React Router example:
import { useEffect } from 'react';

function ProductList() {
  useEffect(() => {
    // User is browsing products — prefetch the product detail chunk
    const link = document.createElement('link');
    link.rel = 'prefetch';
    link.href = '/js/product-detail.chunk.js';
    document.head.appendChild(link);
  }, []);

  return <div>...</div>;
}