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.
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.
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.
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
| Measure | Before | After | Tool |
|---|---|---|---|
| Preload warnings (8 templates) | 19 | 0 | Playwright console check |
| Wasted bytes per docs page view | 284 KB | 0 | DevTools network |
| Duplicate font downloads per view | 2 | 0 | DevTools network |
| LCP p75, docs, mobile | 2.0 s | 1.7 s | web-vitals RUM |
| LCP p75, home, mobile | 1.9 s | 1.6 s | web-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
imagesrcsetandimagesizes, orfetchpriority. - 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.
Related
- Parent: Resource Hints and Navigation Speed — hints and when they help.
- Auditing Unused Preloads — a site-wide audit method.
- Preloading Fonts Without Double Downloads — the font case in detail.
- Fetchpriority Beyond the Hero Image — the alternative to image preloads.
- Preconnect vs DNS-Prefetch on Static Sites — the other hints in the head.
Filed underFixes