Fixing Wasted Preloads Flagged by Chrome

Chrome's console is direct about preloads that do not help:

The resource https://example.com/fonts/inter-600.woff2 was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally.

A preload for 'https://example.com/fonts/inter-400.woff2' is found, but is not used because the request credentials mode does not match. Consider taking a look at crossorigin attribute.

Each warning is bandwidth spent during the most contended part of the page load, on bytes that either are not needed yet or are downloaded twice. On a static site the preloads usually live in a shared layout, so a mistake repeats on every page. This guide starts from those console warnings on a Hugo documentation and marketing site, classifies them by their cause, and fixes each. It belongs to Resource Hints and Navigation Speed; a broader audit method is in Auditing Unused Preloads.

Collect the Warnings Across Templates

One page's console shows one template's problems. A Playwright script visits one page per template and collects every console message mentioning preload:

const found = [];
page.on('console', (m) => { if (/preload/i.test(m.text())) found.push({ url: page.url(), text: m.text() }); });
for (const url of oneUrlPerTemplate) await page.goto(url, { waitUntil: 'load' }), await page.waitForTimeout(4000);

The four-second wait matters: the "not used" warning fires a few seconds after the load event. Across eight templates the site produced 19 warnings, from five distinct causes.

Number of preload warnings by cause across eight templates Horizontal bars. Font preloads missing crossorigin 6 warnings. Preloads in the shared layout for resources used on only some templates 5. Image preloads without imagesrcset while the img used srcset 4. Preload as value not matching the request type 2. Preloads injected by a theme for files that no longer exist 2. Preload warnings by cause Font missing crossorigin 6 Layout-wide, used on some pages 5 Image without imagesrcset 4 Wrong as value 2 Theme preloads stale files 2
19 warnings from one Playwright run over one page per template.

Cause 1: Credentials Mode Mismatch on Fonts

Fonts are always requested in CORS mode, even from the same origin. A preload without crossorigin is a non-CORS request, which the browser cannot reuse for the CORS font request — so the font downloads twice, and the preload's copy is reported as unused. The six font warnings all had this:

<!-- before -->
<link rel="preload" href="/fonts/inter-400.woff2" as="font" type="font/woff2">
<!-- after -->
<link rel="preload" href="/fonts/inter-400.woff2" as="font" type="font/woff2" crossorigin>

This one change removed 92 KB of duplicate font download per page view. Preloading Fonts Without Double Downloads explains the CORS detail.

Cause 2: Preloads in the Shared Layout

Hugo's baseof.html preloaded the home page hero image, the blog heading font weight (600) and a docs-only script, because each had helped on the page it was added for. Every other template downloaded them for nothing. Hugo blocks make template-specific preloads straightforward:

{{/* baseof.html */}}
<head>
  {{ block "preloads" . }}{{ end }}
  …
</head>

{{/* layouts/index.html */}}
{{ define "preloads" }}
  {{ with resources.Get "images/hero.jpg" }}{{ $w := .Resize "1600x webp q75" }}
  <link rel="preload" as="image" href="{{ $w.RelPermalink }}" fetchpriority="high">{{ end }}
{{ end }}

The blog heading font moved to the blog's single template, and the docs script preload was removed entirely — the script was loaded with type="module" at the end of the body, and preloading it had only competed with the CSS.

Preloads in the shared layout before and in templates after Two panels. Before, the shared layout preloaded the regular body font, the 600 weight heading font, the home hero image and a docs script on every page. After, the shared layout preloads only the body font, the home template preloads its hero image, the blog template preloads the heading weight, and the docs script preload is removed. Where each preload belongs Shared layout (before) body font 400 heading font 600 home hero image docs script 4 preloads on every page Per template (after) layout: body font 400 home: hero image blog: heading font 600 docs script: none 1–2 preloads per page
The test is simple: a preload belongs in a template only if every page using that template renders the resource above the fold.

Cause 3: Image Preloads That Picked the Wrong Width

The home page preloaded hero-1600.webp, while the <img> had a srcset from 480 to 1920 widths. On phones the browser chose the 640 width for the <img>, so the 1600 preload was downloaded and never used — 180 KB wasted on exactly the devices that could least afford it. A responsive preload must describe the same choice as the image:

<link rel="preload" as="image" fetchpriority="high"
      imagesrcset="/img/hero-480.webp 480w, /img/hero-960.webp 960w, /img/hero-1600.webp 1600w"
      imagesizes="100vw">

On other templates where the LCP image is in the initial HTML, the preload was dropped in favour of fetchpriority="high" on the <img> itself, which cannot mismatch. Fetchpriority Beyond the Hero Image covers that choice.

Cause 4: The Wrong as Value

Two preloads used as="script" for a JSON search index fetched with fetch(). A preload's as determines the request's destination and priority; a fetch request has destination "empty", so as="script" never matched. The correct value for data fetched with fetch() is as="fetch" with crossorigin matching the fetch's mode. In this case the index was only needed after the reader opened search, so the preload was removed instead — preloading it at all was the real mistake.

Cause 5: Theme Preloads for Files That No Longer Exist

The theme's head partial preloaded /css/fonts.css and /js/vendor.js by fixed path. After an earlier refactor, neither file existed; each produced a 404 and a console warning on every page. Overriding the partial removed them. A post-build check now verifies that every href in a <link rel="preload"> exists in the output.

Checks that run on every build to catch wasted preloads Four steps. A post-build script checks every preload href exists in the output. A Playwright run loads one page per template and waits four seconds after load. It collects any console warning mentioning preload. Any warning fails the pull request with the template URL and message. Keeping preloads honest Preload hrefs exist post-build scan One page per template wait 4 s after load Collect warnings console messages Fail on any URL + message
The Playwright run adds about 40 seconds for eight templates.

Preloads Added by Tools You Did Not Configure

Not every preload on the site was written by hand. Three came from tooling: a Hugo theme partial (Cause 5), an analytics vendor's snippet that injected <link rel="preload"> for its own script at runtime, and Cloudflare's Early Hints, which had turned the theme's stale preloads into 103 Early Hints responses sent before the HTML. Early Hints are useful — they let the browser start fetching critical resources while the server is still preparing the page — but they amplify whatever preloads the HTML contains, including wrong ones, because the browser acts on them even earlier. After the cleanup, the Early Hints sent for each template matched the corrected preload list, as described in Early Hints (103) on Cloudflare.

The vendor's injected preload was harder to see, because it never appeared in the built HTML. The Playwright console check caught it on the first run. The vendor's documentation offered a setting to disable it, which was used; where no such setting exists, loading the vendor script later removes the preload's benefit and its cost together.

How Many Preloads Is Too Many?

After the fixes, each template has one or two preloads: the body font, and on templates whose LCP element is an image, that image. Every additional preload competes for the same early bandwidth, and on a slow mobile connection the first second has room for little beyond the HTML, CSS and one or two critical files. The team's rule is that a new preload needs a WebPageTest comparison in the pull request showing it moves LCP or first render by at least 100 ms on a mobile profile, and that it lives in the narrowest template that uses it.

Measured Impact

MeasureBeforeAfterTool
Preload warnings (8 templates)190Playwright console check
Wasted bytes per docs page view284 KB0DevTools network
Duplicate font downloads per view20DevTools network
LCP p75, docs, mobile2.0 s1.7 sweb-vitals RUM
LCP p75, home, mobile1.9 s1.6 sweb-vitals RUM

LCP improved on docs pages without any change to the LCP element itself: the 284 KB of wasted preloads had been competing with the CSS and the body font during the first second.

Pitfalls & Rollback

  • Font preloads without crossorigin. They always download twice.
  • Template-specific preloads in shared layouts. Every other page pays for them.
  • Single-URL preloads for responsive images. Use imagesrcset and imagesizes, or fetchpriority.
  • Preloading things needed later. Preload only what the first render needs.
  • Rollback: each preload is a line in a template; the console check shows immediately if a removed preload was actually used.

Conclusion

Chrome's preload warnings point at five recurring causes on static sites: fonts without crossorigin, template-specific preloads in shared layouts, responsive images preloaded at a single width, mismatched as values and stale theme preloads. Fixing them removed 284 KB of wasted downloads per docs page and improved mobile LCP by about 300 ms, and a Playwright check over one page per template keeps the console clean.

FAQ

What does the warning about a preload not used within a few seconds mean?

Chrome downloaded a resource because of a link rel preload, but nothing on the page requested it within a few seconds of the load event. Either the page does not use it, uses it much later, or requests it in a way that does not match the preload, so the browser treated the real request as a different resource and the preloaded copy was wasted.

Why does Chrome say a preload was found but not used because the credentials mode does not match?

The preload and the real request differ in whether they are CORS requests. Fonts are always fetched with CORS, so a font preload without the crossorigin attribute is fetched without CORS, cannot be reused for the real font request, and the font downloads twice. Adding crossorigin to the preload fixes it.

Can a preload be correct on one page and wasted on another?

Yes, and that is the most common case on static sites. A preload placed in a shared layout runs on every page, but the resource may only be used on some templates, such as a hero image on the home page or a font weight used only in blog headings. Move such preloads into the templates that use the resource.

Are responsive image preloads a source of waste?

They can be. A preload of a single image URL is wasted when the img element uses srcset and the browser chooses a different width. Use imagesrcset and imagesizes on the preload, matching the img exactly, or use fetchpriority high on the img instead of a preload.

Filed underFixes