Resource Hints Explained: Preload, Preconnect, Prefetch, and Early Hints for Faster Sites

A practical guide to browser resource hints — preload, preconnect, prefetch, dns-prefetch, fetchpriority, modulepreload, and HTTP 103 Early Hints. Learn when each hint delivers real gains, how to avoid mistakes that hurt loading speed, and how to audit your setup.

Every millisecond counts when a browser loads your page. Between DNS lookups, TLS handshakes, and resource discovery, the default loading waterfall leaves plenty of room for optimization. Resource hints — preload, preconnect, prefetch, dns-prefetch, and the newer HTTP 103 Early Hints — give you precise control over how and when the browser fetches critical assets. Used correctly, they can shave hundreds of milliseconds off your Largest Contentful Paint (LCP) and dramatically improve perceived performance.

Used incorrectly? They'll actually make things worse. I've seen it happen more times than I'd like to admit.

This guide covers every resource hint available in 2026, explains when each one delivers real gains, walks through common pitfalls that actually hurt performance, and shows you how to audit and measure your implementation.

How Browsers Discover and Prioritize Resources

Before we dive into resource hints, it helps to understand why they exist in the first place. When a browser receives an HTML document, it parses the markup top-to-bottom using two mechanisms:

  • The HTML parser — processes the DOM sequentially, blocking on synchronous scripts and render-blocking CSS.
  • The preload scanner — a secondary, lookahead parser that scans ahead in the raw HTML to find resources (images, scripts, stylesheets) and starts fetching them before the main parser reaches them.

The preload scanner is remarkably effective. But it has blind spots. It can't discover:

  • CSS background images (defined inside stylesheets, not in the HTML)
  • Fonts loaded via @font-face rules — these aren't discovered until the CSS is parsed and a matching DOM node triggers a font request
  • JavaScript-initiated fetches (dynamic imports, XHR/fetch calls)
  • Resources on third-party origins the browser hasn't connected to yet

Resource hints exist to close these gaps. They tell the browser about resources and connections it can't discover on its own — or can't discover early enough.

preconnect — Warm Up Third-Party Connections

The preconnect hint instructs the browser to perform DNS resolution, TCP handshake, and TLS negotiation with a specified origin before any actual resource request is made. This eliminates 100–300 ms of connection setup latency on the critical path.

<!-- Warm up the connection to your CDN -->
<link rel="preconnect" href="https://cdn.example.com">

<!-- Google Fonts requires two origins -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>

When to Use preconnect

  • Third-party fonts — Google Fonts, Adobe Fonts, or any self-hosted font CDN.
  • Analytics and tag managers — Google Analytics, GTM, or your analytics endpoint.
  • API endpoints on separate domains — if your frontend calls api.example.com, preconnecting saves real time.
  • CDN origins — when your images or static assets live on a different domain.

preconnect Limits and Pitfalls

Here's the thing most guides don't emphasize enough: each preconnect opens a socket, performs a TCP handshake, and negotiates TLS. That has a real cost — roughly 3 KB for the TLS certificate alone, plus CPU overhead on both client and server. And Chrome closes unused preconnected sockets after just 10 seconds.

Best practice: limit preconnect to 4–6 origins maximum. If you need to hint at more domains, fall back to the lighter dns-prefetch for the less critical ones.

dns-prefetch — The Lightweight Alternative

dns-prefetch performs only DNS resolution — translating a domain name to an IP address — without establishing a full connection. It costs almost nothing and is a safe, broad optimization.

<link rel="dns-prefetch" href="https://analytics.example.com">
<link rel="dns-prefetch" href="https://third-party-widget.com">

Combine dns-prefetch With preconnect

The recommended pattern is to pair preconnect with a dns-prefetch fallback for browsers that don't support preconnect. However — and this one bit me personally — there's a Safari-specific pitfall you need to know about.

Safari requires separate <link> tags. Putting both preconnect and dns-prefetch in a single rel attribute causes Safari to skip the tag entirely.

<!-- Correct: separate tags for Safari compatibility -->
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="dns-prefetch" href="https://fonts.gstatic.com">

<!-- Incorrect: Safari ignores this entirely -->
<link rel="preconnect dns-prefetch" href="https://fonts.gstatic.com">

preload — Force-Fetch Critical Resources

Unlike true resource hints, preload is a mandatory directive. When you add <link rel="preload">, you're commanding the browser to fetch the resource at high priority, regardless of whether it has encountered it in the document yet. This makes preload the most powerful — and honestly, the most dangerous — of all resource hints.

<!-- Preload the LCP hero image -->
<link rel="preload" as="image" href="/images/hero.avif"
      fetchpriority="high">

<!-- Preload a critical font -->
<link rel="preload" as="font" type="font/woff2"
      href="/fonts/Inter-Regular.woff2" crossorigin>

<!-- Preload critical CSS not in the initial HTML -->
<link rel="preload" as="style" href="/css/above-the-fold.css">

The as Attribute Is Required

Every preload must include the as attribute, which tells the browser what type of resource it's fetching. Valid values include image, font, style, script, fetch, and document. If the as attribute is missing or incorrect, the browser fetches the resource with low priority and may download it twice. Not great.

The crossorigin Gotcha

This is one of the most common preload mistakes I see, and it's subtle. Fonts are always fetched using CORS, even from the same origin. If you preload a font without the crossorigin attribute, the browser makes two requests — one from the preload (without CORS) and one from the @font-face rule (with CORS) — because the cache entries don't match.

You can easily spot this in the DevTools Network panel as a duplicate request. If you see your font being downloaded twice, this is almost certainly the culprit.

How Many Resources Should You Preload?

The golden rule: preload 2–4 critical resources at most. Every preloaded resource competes for bandwidth with resources the browser would discover naturally. In real-world testing, pages with 10+ preloads actually loaded slower — one test showed a page rendering 0.9 seconds faster after removing excessive preloads. Let that sink in.

Only preload resources that meet all three criteria:

  1. The resource is needed for initial render or LCP.
  2. The browser's preload scanner can't discover it early enough.
  3. The resource is on the critical path (above the fold).

fetchpriority — Fine-Tune Loading Priority

The fetchpriority attribute (part of the Fetch Priority API) complements resource hints by letting you adjust the relative priority of a resource without preloading it. It accepts three values: high, low, and auto (the default).

<!-- Boost the LCP image priority -->
<img src="/images/hero.webp" alt="Hero banner"
     fetchpriority="high" width="1200" height="600">

<!-- Lower priority for below-the-fold images -->
<img src="/images/footer-logo.png" alt="Logo"
     fetchpriority="low" loading="lazy">

<!-- Deprioritize a non-critical script -->
<script src="/js/analytics.js" fetchpriority="low" async></script>

fetchpriority vs. preload — When to Use Which

These two solve different problems, and it's worth being clear about the distinction:

  • preload solves a discovery problem — the browser doesn't know about the resource soon enough.
  • fetchpriority solves a priority problem — the browser knows about the resource but assigns it the wrong priority.

For LCP images that are directly in the HTML (an <img> tag), fetchpriority="high" alone is usually sufficient. You don't need to preload it because the preload scanner already discovers it. Adding both preload and fetchpriority="high" is most useful for late-discovered resources like CSS background images.

Real-World Impact

The numbers here are pretty compelling. Google's own tests showed LCP improving from 2.6 seconds to 1.9 seconds — a 27% improvement — just by adding fetchpriority="high" to the LCP element. Etsy reported a 4% LCP improvement, and other sites have seen 20–30% gains in specific scenarios.

That's a lot of performance for one attribute.

prefetch — Preload Resources for the Next Page

prefetch hints tell the browser to fetch resources during idle time that might be needed for a future navigation. Prefetched resources are stored in the HTTP cache at the lowest priority, so they won't compete with the current page's assets.

<!-- Prefetch the likely next page -->
<link rel="prefetch" href="/pricing">

<!-- Prefetch a critical script for the next page -->
<link rel="prefetch" as="script" href="/js/checkout-bundle.js">

When prefetch Makes Sense

  • Multi-step flows — during login, prefetch the dashboard assets.
  • Paginated content — prefetch the next page of results.
  • High-confidence navigation — analytics show 80%+ of users click a specific link.

When to Avoid prefetch

  • Low-confidence navigations — wasting bandwidth on speculative fetches frustrates users on metered connections.
  • Safari — as of early 2026, Safari doesn't support prefetch. Consider using the Speculation Rules API (covered in a separate article) for cross-browser speculative loading.
  • Data-saver mode — check the Save-Data header and skip prefetch for users who opt into reduced data usage.

modulepreload — Preload ES Modules

Standard preload fetches a single file but doesn't parse or compile it. modulepreload goes further: it fetches the module, parses it, compiles it, and puts it into the module map ready for execution. For module-heavy applications, this eliminates the waterfall of sequential module fetches.

<!-- Preload an ES module and its dependencies -->
<link rel="modulepreload" href="/js/app.js">
<link rel="modulepreload" href="/js/utils.js">

<!-- Then use the module normally -->
<script type="module" src="/js/app.js"></script>

Without modulepreload, the browser has to download app.js, parse it to discover import statements, then fetch each dependency — creating a chain of sequential requests. With modulepreload, all modules are fetched in parallel, compiled, and ready before execution begins. It's a big difference if your app has a deep dependency tree.

HTTP 103 Early Hints — Send Hints Before the HTML

Early Hints represent the latest evolution in resource loading optimization. Instead of waiting for the server to generate the full HTML response, the server sends an interim 103 status code with Link headers while it's still processing the request.

HTTP/2 103 Early Hints
Link: </css/main.css>; rel=preload; as=style
Link: <https://fonts.gstatic.com>; rel=preconnect; crossorigin

HTTP/2 200 OK
Content-Type: text/html
...

The browser receives these hints during the server's "think time" — that period when the server is querying databases, calling APIs, or rendering templates — and starts fetching resources immediately. By the time the actual HTML arrives, your critical CSS and fonts may already be downloaded.

Pretty clever, right?

Performance Gains From Early Hints

Testing shows that without 103 Early Hints, LCP images appear up to 45% slower. The improvement is even greater when the 103 response includes stylesheets, because the browser can begin fetching render-blocking CSS before the HTML even arrives.

Implementation Requirements

  • Protocol — Early Hints require HTTP/2 or HTTP/3. Most browsers ignore 103 responses over HTTP/1.1.
  • Cacheability — resources preloaded via Early Hints are stored in the HTTP cache. Non-cacheable resources will be fetched twice (once from the hint, once from the document), so only hint at cacheable assets.
  • Server support — NGINX (with the early_hints directive), Apache (via mod_http2), Cloudflare, and Fastly all support 103 Early Hints. CDN-level Early Hints (like Cloudflare's automatic implementation) can be enabled with zero server configuration.
  • Limitations — responsive images with imagesrcset can't be preloaded via Early Hints because the viewport isn't defined until the document is created.

Setting Up Early Hints on NGINX

server {
    listen 443 ssl http2;

    location / {
        # Enable Early Hints from upstream
        early_hints on;

        proxy_pass http://backend;
    }
}

# Or send hints directly
location /page {
    add_header Link "</css/critical.css>; rel=preload; as=style" early;
    add_header Link "<https://cdn.example.com>; rel=preconnect" early;
    proxy_pass http://backend;
}

Putting It All Together — A Complete Strategy

So, let's bring everything together. Here's a practical resource hints configuration for a typical content site with third-party fonts, a CDN, and analytics:

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <!-- 1. Preconnect to critical third-party origins -->
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link rel="preconnect" href="https://cdn.example.com">

  <!-- 2. dns-prefetch fallback + less critical origins -->
  <link rel="dns-prefetch" href="https://fonts.googleapis.com">
  <link rel="dns-prefetch" href="https://fonts.gstatic.com">
  <link rel="dns-prefetch" href="https://www.google-analytics.com">

  <!-- 3. Preload late-discovered critical resources -->
  <link rel="preload" as="font" type="font/woff2"
        href="https://fonts.gstatic.com/s/inter/v18/regular.woff2"
        crossorigin>
  <link rel="preload" as="image" href="/images/hero.avif"
        fetchpriority="high">

  <!-- 4. Critical CSS inline, rest loaded normally -->
  <link rel="stylesheet" href="/css/main.css">
</head>
<body>
  <!-- LCP image with fetchpriority -->
  <img src="/images/hero.avif" alt="Hero image"
       fetchpriority="high" width="1200" height="600">

  <!-- Below-the-fold images use lazy loading -->
  <img src="/images/feature.webp" alt="Feature"
       loading="lazy" fetchpriority="low">

  <!-- Prefetch likely next navigation -->
  <link rel="prefetch" href="/pricing">
</body>

Auditing Your Resource Hints

Incorrect resource hints are worse than no resource hints. Seriously. Here's how to audit your current implementation:

Step 1: Check for Unused Preloads

Chrome DevTools logs a console warning if a preloaded resource isn't used within 3 seconds of the load event: "The resource was preloaded using link preload but not used within a few seconds." Open the Console tab after loading your page and look for these warnings.

Step 2: Spot Double Downloads

In the DevTools Network panel, enable the "Priority" column and look for duplicate requests to the same URL. This usually indicates a missing or mismatched crossorigin attribute on a font preload.

Step 3: Verify Connection Warmups

In WebPageTest, inspect the connection view waterfall. Each preconnected origin should show DNS, TCP, and TLS completing before the first resource request. If these steps happen concurrently with or after the request, your preconnect tags are either placed too late in the document or are being ignored.

Step 4: Measure Before and After

Always run a before-and-after test using Lighthouse, WebPageTest, or real user monitoring (RUM). Track these metrics specifically:

  • LCP — should improve if you're preloading/prioritizing the LCP resource correctly.
  • FCP — should improve if you're preconnecting to font or CSS origins.
  • TTFB impact — Early Hints should reduce effective TTFB for sub-resources.
  • Total transfer size — shouldn't increase significantly from speculative prefetches.

Common Mistakes That Hurt Performance

After looking at hundreds of sites (and making a few of these mistakes myself), here are the resource hint problems that come up over and over again:

1. Preloading Everything

When everything is high priority, nothing is. Preloading 10+ resources forces them all to compete for bandwidth, delaying the resources that actually matter. One real-world test showed a page rendering 0.9 seconds faster after removing unnecessary preloads.

2. Preloading Resources the Browser Already Discovers

If your main stylesheet is linked in the <head> of your HTML, the preload scanner finds it almost instantly. Adding a preload for it wastes a request slot and can actually delay other resources. Don't help the browser with things it already knows.

3. Missing crossorigin on Font Preloads

Fonts always require CORS. A preload without crossorigin creates a non-CORS cache entry, and the @font-face rule creates a CORS request that doesn't match — resulting in a double download. This is the number one font preload mistake out there.

4. Stale preconnect Tags

After removing a third-party service, the preconnect tag often stays behind in the HTML. The browser opens a socket, negotiates TLS, and then never uses the connection — wasting CPU and bandwidth for nothing. (Ask me how I know.)

5. Preloading Non-Cacheable Resources via Early Hints

Resources preloaded through HTTP 103 Early Hints must be cacheable. If the resource has Cache-Control: no-store, the browser fetches it from the Early Hint, discards it, then fetches it again when the HTML requests it — doubling the network cost.

6. Lazy-Loading the LCP Image

Never apply loading="lazy" to the LCP element. Lazy loading defers the image request until it enters the viewport, which is the exact opposite of what you want for the largest contentful element. Use fetchpriority="high" instead.

Quick Reference: Resource Hints Decision Matrix

Use this decision tree to choose the right hint for each scenario:

  • You need a third-party resource on the current pagepreconnect (plus dns-prefetch fallback)
  • You need a critical resource the browser can't discover earlypreload (with correct as and crossorigin)
  • The browser discovers the resource but assigns wrong priorityfetchpriority="high" or fetchpriority="low"
  • You want to speed up the next page navigationprefetch or Speculation Rules API
  • Your server has long response times → HTTP 103 Early Hints
  • Your app uses ES modulesmodulepreload

Frequently Asked Questions

What is the difference between preload and prefetch?

preload fetches a resource at high priority for the current page and is mandatory — the browser must execute it. prefetch fetches a resource at the lowest priority during idle time for a future navigation and is speculative — the browser may skip it if bandwidth is limited. Use preload for resources needed now; use prefetch for resources needed on the next page.

How many resources should I preload on a single page?

Limit preloads to 2–4 truly critical resources. Every preload competes for bandwidth with naturally discovered resources. Testing consistently shows that pages with 10+ preloads load slower than pages with fewer, well-targeted preloads. Only preload resources that are on the critical rendering path and can't be discovered by the browser's preload scanner.

Does preconnect work with HTTP/1.1 or only HTTP/2?

preconnect works with both HTTP/1.1 and HTTP/2 connections. It performs DNS resolution, TCP handshake, and TLS negotiation regardless of the HTTP version. However, HTTP 103 Early Hints (which can include preconnect directives) require HTTP/2 or HTTP/3 — most browsers ignore 103 responses over HTTP/1.1.

Why does Safari not support prefetch?

As of early 2026, Safari has chosen not to implement the prefetch resource hint. Apple hasn't publicly stated the specific reason, but it likely relates to privacy and data usage concerns — prefetch speculatively downloads resources that may never be used. For cross-browser speculative loading, consider the Speculation Rules API, which Safari has begun to explore, or implement client-side prefetching via JavaScript using the Intersection Observer API to detect when links enter the viewport.

Can I use resource hints via HTTP headers instead of HTML link tags?

Yes, and honestly this approach doesn't get enough attention. All resource hints can be delivered as HTTP Link headers, which can be more efficient since the browser processes them before parsing any HTML. This is especially powerful when combined with 103 Early Hints, where the server sends Link headers during its processing time. The syntax is: Link: </css/main.css>; rel=preload; as=style. This approach is particularly useful when you don't control the HTML output (e.g., behind a CDN or reverse proxy).

About the Author Editorial Team

Our team of expert writers and editors.