# Preload, preconnect, prefetch: which resource hint when

> Resource hints tell the browser what to fetch before it would find out on its own. One hint per job, the exact syntax for images, fonts and scripts, and the mistakes that make hints slower than none.

Updated 2026-09-25 · Speed & Core Web Vitals · HTML version: https://getreport.app/guides/preload-preconnect-prefetch-which-hint-when

A browser reads your HTML from top to bottom and only learns about a file when it reaches the tag that names it. A hero image set in CSS, a font named inside a stylesheet, a script loaded by another script: each is discovered late, and each late discovery is a few hundred milliseconds nobody sees on a laptop and everybody sees on a phone. Resource hints are one-line `<link>` tags that tell the browser earlier. Used on the right three or four files they move Largest Contentful Paint by whole seconds; used on thirty files they slow the page down. This guide gives one hint per job and the syntax that actually works.

## Quick answer

| Job | Hint | Example |
| --- | --- | --- |
| A third-party origin you will certainly use early (font host, image CDN) | `preconnect` | `<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>` |
| An origin you might use, or a browser without preconnect | `dns-prefetch` | `<link rel="dns-prefetch" href="https://www.googletagmanager.com">` |
| A critical file the parser finds late: the LCP image behind CSS, an above-the-fold font | `preload` | `<link rel="preload" as="image" href="https://getreport.app/img/hero.webp">` |
| The LCP image that is already an `<img>` in the HTML | `fetchpriority="high"` | `<img src="https://getreport.app/img/hero.webp" fetchpriority="high">` |
| A JavaScript module the page needs at start-up | `modulepreload` | `<link rel="modulepreload" href="https://getreport.app/js/app.js">` |
| A file the *next* page needs | `prefetch` | `<link rel="prefetch" href="https://getreport.app/checkout.css">` |

Two to four preconnects and one to three preloads per page is the normal amount. More than that and the hints compete with the page itself.

## Why resource hints matter

The browser's parser is fast at finding what is in the HTML: every `<img src>`, `<link rel="stylesheet">` and `<script src>` is requested within milliseconds of the document arriving. What it cannot see is anything one level deeper. A font is named in a stylesheet, so the request starts only after the CSS has downloaded and been parsed. A hero image set with `background-image` waits for the CSS too, and then for layout, because the browser does not fetch backgrounds for elements it has not placed yet. A slider that inserts its images with JavaScript waits for the script.

Each of those waits is a full chain: download the CSS (one round trip plus transfer), parse it, then start the image. On the lab's simulated phone connection a round trip is 150 ms, so a font discovered through a stylesheet on another host costs a DNS lookup, a TCP connection, a TLS handshake and the request before the first byte, easily 600 ms after the HTML arrived. A `preconnect` removes the first three; a `preload` removes the wait for the CSS entirely.

The cost is that every hint is a request the browser makes on trust. A preloaded file competes for bandwidth with the CSS and the HTML, and a preconnect holds a socket open whether it is used or not. That is why the rule is one hint per critical file, not one per file.

## How getReport checks it

> **Free tool:** [Website speed test](https://getreport.app/tools/speed-test): Lighthouse lab results and real-user Core Web Vitals for any page, mobile and desktop, with a filmstrip, a request waterfall and a fix for every slow part. Free, no signup.

The speed test runs Lighthouse on the page through Google's PageSpeed Insights (mobile by default) and turns the audits into findings. Three of them are where hints show up:

> **Check: Largest Contentful Paint.** LCP is when the biggest thing on screen — usually the hero image or headline — finishes loading. Visitors judge "is this site slow?" on it, and Google uses it for ranking.
>
> 1. Find the LCP element (named in your report) and make it lighter, earlier or both — a compressed WebP/AVIF, sized to the screen, without lazy loading.
> 2. Preload it with <link rel="preload" as="image"> or add fetchpriority="high" on the <img>.
> 3. Cut what comes before it — render-blocking CSS/JS and slow server responses push LCP back.

The [LCP finding](https://getreport.app/learn/largest-contentful-paint) names the element. If it is an image that is not in the HTML as an `<img>`, a preload is the fix; if it is an `<img>`, `fetchpriority="high"` is enough. The waterfall under the panel shows when the file started downloading relative to the HTML, which is the delay a hint removes.

> **Check: No render-blocking resources delay the first paint.** Stylesheets and scripts in <head> stop the browser from drawing anything until they have downloaded. Each one adds a round trip before the visitor sees the page.
>
> 1. Add defer or async to scripts that are not needed before the first paint, or move them to the end of <body>.
> 2. Inline the CSS needed for the first screen and load the rest asynchronously (WordPress: "Optimize CSS delivery" in WP Rocket / LiteSpeed Cache).
> 3. Remove unused plugin CSS/JS from pages that do not need it.

![The render-blocking resources finding opened: a list of stylesheets and scripts from the head with the estimated delay for each, and the fix steps beneath](https://getreport.app/guides/img/preload-preconnect-prefetch-which-hint-when/render-blocking.webp "Each stylesheet or script in the list is one round trip before the first paint; hints help with what those files load next, not with the files themselves.")

Hints do not fix render-blocking files (a preloaded stylesheet still blocks), but the list tells you which stylesheet the fonts and the hero come from, and therefore what to preload. The [render-blocking resources guide](https://getreport.app/guides/render-blocking-resources) covers the files themselves.

> **Check: Web fonts show text while loading.** Without font-display, browsers show blank text for up to 3 s while a web font downloads. Visitors stare at empty headings.
>
> 1. Add font-display swap (or optional) to every @font-face rule.
> 2. For Google Fonts, append &display=swap to the stylesheet URL; self-hosted fonts get it in the @font-face block.
> 3. Preload the one or two fonts used above the fold.

A font that hides text needs [`font-display: swap`](https://getreport.app/learn/font-display) first; a preload on top of that makes the swap happen sooner and, for the one or two fonts used above the fold, often before the first paint, so no swap is visible at all.

## Step by step

### 1. Preconnect to the origins you will certainly use

`preconnect` does the DNS lookup, TCP connection and TLS handshake now, so the first real request to that origin skips them. Use it for origins the page needs within the first second: the font host, the image CDN, the tag manager if it loads in `<head>`.

```html
<!-- In <head>, before any stylesheet -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preconnect" href="https://cdn.example-shop.hr">
```

`crossorigin` matters for fonts: browsers fetch fonts in CORS mode, which uses a separate connection from normal requests. A preconnect to `fonts.gstatic.com` without `crossorigin` opens a connection the font request cannot use. Stylesheets, images and scripts without a `crossorigin` attribute use the plain connection, so their preconnect goes without it.

Keep it to two to four origins. Browsers close an unused preconnected socket after about ten seconds, and each one costs a little CPU and, on some phones, a slot from a small pool of parallel connections.

### 2. Use dns-prefetch as the cheap fallback

`dns-prefetch` only resolves the hostname, which is the cheapest of the three steps and safe to do for six or eight origins. Put it on origins the page uses later or maybe (an analytics host, an embed that appears below the fold), and pair it with preconnect for the critical ones so older browsers that ignore `preconnect` still get something:

```html
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="dns-prefetch" href="https://fonts.gstatic.com">
<link rel="dns-prefetch" href="https://www.googletagmanager.com">
<link rel="dns-prefetch" href="https://www.youtube.com">
```

### 3. Preload the critical files the parser finds late

`preload` fetches a file at high priority without waiting for whatever would normally reference it. It needs `as` (so the browser knows the priority and the right `Accept` header; MDN's [rel=preload reference](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel/preload) lists every value) and it must describe the request exactly as the page will make it, including `crossorigin` and, for images with `srcset`, the same candidates.

The LCP image set in CSS or by a script:

```html
<link rel="preload" as="image" href="https://getreport.app/img/hero-1200.webp" fetchpriority="high">
```

The same image as a responsive set, so the browser preloads the size it will actually use:

```html
<link rel="preload" as="image"
      imagesrcset="/img/hero-600.webp 600w, /img/hero-1200.webp 1200w, /img/hero-1800.webp 1800w"
      imagesizes="100vw" fetchpriority="high">
```

The one or two fonts used in the first screen (always `crossorigin`, even from your own host, because font requests are CORS requests):

```html
<link rel="preload" as="font" type="font/woff2" href="https://getreport.app/fonts/instrument-sans-var.woff2" crossorigin>
```

A script that is discovered late but needed early, for example a script another script injects:

```html
<link rel="preload" as="script" href="https://getreport.app/js/consent.js">
```

If any attribute differs from the real request (`crossorigin` missing on a font, a query string that does not match, a different image candidate), the browser downloads the file twice and prints a warning in the console: "The resource … was preloaded using link preload but not used within a few seconds from the window's load event". That warning is your test.

### 4. Prefer fetchpriority when the file is already in the HTML

If the hero is an `<img>` in the HTML, the parser finds it immediately; the problem is not discovery but priority. Browsers start images at low priority until layout tells them the image is in the viewport. `fetchpriority="high"` removes that guess, and it is one attribute rather than a duplicated URL in the head:

```html
<img src="https://getreport.app/img/hero-1200.webp"
     srcset="/img/hero-600.webp 600w, /img/hero-1200.webp 1200w"
     sizes="100vw" width="1200" height="675"
     alt="Autumn collection: wool coats on a rack"
     fetchpriority="high">
```

The attribute also works the other way. `fetchpriority="low"` on a carousel's second and third slides, a below-the-fold script or a non-critical `fetch()` call keeps them from competing with the LCP image:

```html
<script src="https://getreport.app/js/reviews-widget.js" defer fetchpriority="low"></script>
```

Use `high` on exactly one image per page. Two "high" images are two images at normal priority.

### 5. Preload JavaScript modules with modulepreload

For a site built on ES modules (`<script type="module">`), the browser discovers imports one file at a time: it downloads `app.js`, parses it, finds `import './cart.js'`, downloads that, and so on. `modulepreload` fetches and compiles the dependency tree in parallel:

```html
<link rel="modulepreload" href="https://getreport.app/js/app.js">
<link rel="modulepreload" href="https://getreport.app/js/cart.js">
<link rel="modulepreload" href="https://getreport.app/js/vendor/lit.js">
```

Build tools (Vite, Astro, SvelteKit, Next.js) emit these automatically for the entry chunks; the manual version is for hand-rolled module setups. Do not use `rel="preload" as="script"` for modules: it fetches without the module credentials mode and the file is downloaded twice.

### 6. Prefetch what the next page needs, sparingly

`prefetch` downloads a file at the lowest priority when the browser is idle, so it is in cache when the visitor navigates. Good candidates are the stylesheet or main script of the page most visitors go to next (the product page from a category, the checkout from the cart):

```html
<link rel="prefetch" href="https://getreport.app/css/product.css">
<link rel="prefetch" href="https://getreport.app/js/product.js">
```

Prefetch is a guess about the future paid for with the visitor's data. Browsers treat it as optional and may skip it on slow connections; you can be polite yourself by not emitting it when `navigator.connection.saveData` is true. Do not prefetch whole HTML pages on a site with many possible next pages, and never prefetch anything on the checkout flow. Lighthouse does not reward prefetch (it measures one page), so the only way to see the gain is a real second-page navigation in DevTools.

### 7. Consider 103 Early Hints if your CDN offers them

Everything above waits for the HTML to arrive. A server that knows the critical files can send a `103 Early Hints` response with `Link` headers while it is still generating the page, and Chrome and Firefox start the preconnects and preloads from it before the HTML exists:

```text
HTTP/1.1 103 Early Hints
Link: </fonts/instrument-sans-var.woff2>; rel=preload; as=font; crossorigin
Link: <https://cdn.example-shop.hr>; rel=preconnect
```

This is most useful when TTFB is high (an uncached CMS page taking 800 ms lets the browser fetch the font in the meantime). Cloudflare can generate Early Hints from the `Link` headers your origin sends; on your own server it depends on the web server version. It is a refinement, not a first step.

## Platform notes

### WordPress

- **Perfmatters**: the Preload tab has fields for Preload (URL, `as`, optional device and `crossorigin`), Preconnect and DNS Prefetch, plus "Preload Critical Images" and a fetch priority option for the LCP image. Fill in the font and the hero, nothing else.
- **WP Rocket**: the Preload tab covers "Prefetch DNS Requests" (one origin per line) and "Preload Fonts" (paths to WOFF2 files); use it for the above-the-fold fonts only.
- **A child theme** without a plugin, using WordPress's own hooks so the hints are printed early in `<head>`:

```php
// functions.php (child theme): preconnect to the font host, preload the front-page hero
add_filter('wp_resource_hints', function (array $urls, string $relation_type): array {
    if ('preconnect' === $relation_type) {
        $urls[] = ['href' => 'https://fonts.gstatic.com', 'crossorigin' => 'anonymous'];
    }
    return $urls;
}, 10, 2);

add_action('wp_head', function (): void {
    if (!is_front_page()) {
        return;
    }
    $hero = get_stylesheet_directory_uri() . '/img/hero-1200.webp';
    echo '<link rel="preload" as="image" href="' . esc_url($hero) . '" fetchpriority="high">' . "\n";
}, 1);
```

Google Fonts loaded through the theme already come with a preconnect in recent WordPress versions; check the page source before adding a second one.

### Shopify

Themes based on Dawn preconnect to `cdn.shopify.com` and preload the theme stylesheet in `theme.liquid`. For the hero, use `fetchpriority="high"` on the first section's image (edit the section's Liquid and add the attribute to the `image_tag` filter's parameters) rather than a preload; Shopify's image URLs carry size parameters that are easy to mismatch.

### Static sites and custom

Put the hints in the `<head>` template above the first stylesheet. Frameworks with an image component (Next.js, Astro, Nuxt) accept a `priority` or `fetchpriority` prop that emits the preload for you; add nothing by hand until you have checked the generated HTML.

## Verify

- The LCP finding in the [speed test](https://getreport.app/tools/speed-test) shows the hero as the element and a lower value; in the waterfall the image now starts within a few hundred milliseconds of the HTML instead of after the CSS.
- In DevTools → Network, enable the Priority column: the preloaded image and fonts show "High" (or "Highest"), scripts with `fetchpriority="low"` show "Low".
- The console shows no "preloaded but not used" warnings after the page has loaded.
- The `font-display` finding passes and the headline does not visibly swap fonts on a throttled reload.
- The Core Web Vitals field bars (28 days of real visitors) move about four weeks after the change; the lab number moves immediately.

## Common mistakes

- **Preloading everything.** Twelve preloads at high priority delay the CSS they were meant to help. Symptom: FCP gets worse after "optimising". Keep one to three.
- **Preloading a font that is not used above the fold.** The browser downloads the italic bold that appears in the footer before the regular weight the headline needs. Preload the weights in the first screen only; give the rest `font-display: swap` and let them arrive when they arrive.
- **Preconnecting to fifteen origins.** Each connection costs work up front and most are never used. Symptom: no measurable gain, sometimes a loss on mobile. Two to four, and `dns-prefetch` for the rest.
- **A preload that does not match the request.** Missing `crossorigin` on a font, or a different image size than the `srcset` picks. Symptom: the file downloads twice and the console warns. Copy the exact URL and attributes from the Network panel.
- **`fetchpriority="high"` on every image in a gallery.** Nothing is high if everything is. One image, the LCP one; `low` for the rest of the carousel.
- **Prefetching on the checkout.** The visitor pays for files for pages they will not visit, on the one flow where every kilobyte of bandwidth matters. Prefetch from the category and product pages, not from the cart.
