The console reports “The resource https://cdn.vendor.example/sdk.js was preloaded using link preload but not used within a few seconds from the window’s load event”, and the Network panel shows the file fetched twice — once by the hint, once by the script tag.

Triage: Establish Which Failure You Have

The warning has two quite different causes, and they need opposite fixes. Either the preload was never claimed at all — the script genuinely does not load on this page — or it was claimed by a request the browser considered different, producing a duplicate fetch. Distinguishing them takes one glance at the Network panel.

  1. Open Network, disable the cache, and filter to the script’s filename.
  2. Count the rows. One row means the preload was never claimed: nothing requested that URL. Two rows means the claim was rejected as a mismatch.
  3. For the two-row case, click each and compare the Request Headers. Look specifically at Origin, Sec-Fetch-Mode and Referer — a mismatch in fetch mode is the usual culprit and is visible here.
  4. Note the Initiator of each row. One should read link and the other script or other.
One warning, two different faults The warning appears in both cases but the Network panel distinguishes them. A single row for the URL means nothing ever requested it, so the preload is simply wrong for this page. Two rows mean a request was made but did not match the preloaded entry, so the hint is correct in principle and wrong in one attribute. one row — never claimed nothing requested this URL the preload is wrong for this page fix: remove it, or gate it two rows — mismatched a request was made and rejected you paid for the bytes twice fix: align the attributes The row count is the whole diagnosis. Everything after it follows from which case you are in.

Root Cause: The Match Is Stricter Than It Looks

A preloaded response sits in a short-lived cache keyed by more than its URL. To claim it, the consuming request must agree on the destination type (as), the CORS mode (crossorigin), the referrer policy, and any integrity value — plus the URL, character for character, including query string. Disagree on any one and the entry is not a candidate; the browser issues a fresh request, and the preloaded bytes expire unused a few seconds later.

For third-party scripts, two of those rules account for nearly all real cases. The crossorigin mismatch is the classic: a hint without the attribute paired with a <script crossorigin> element, or the reverse, which puts the two requests in different CORS modes and therefore different cache buckets. The other is a URL that is nearly identical — a cache-busting query parameter added by one side, a protocol-relative form, or a trailing difference introduced by a tag manager rewriting the element.

The unclaimed case has a different flavour and is usually architectural rather than a typo. A preload emitted on every page for a script that only loads on some of them will warn on all the others; a preload for a consent-gated vendor will warn on every visit where consent is pending or declined, because the claim arrives long after the entry has been evicted.

The four properties that must agree Four properties are compared between the preload hint and the consuming request: the destination type, the CORS mode, the referrer policy and the integrity value, alongside the URL itself. All must agree for the preloaded response to be reused. The CORS mode and the exact URL account for most real-world mismatches on third-party scripts. as / destination must be script for a script — a wrong value buckets it elsewhere crossorigin the most common mismatch on third-party URLs referrerpolicy differs silently when one side inherits a document policy integrity present on one side only is a mismatch, not a bonus Plus the URL, character for character. A cache-busting parameter on one side is a mismatch.

Resolution: Align, or Remove

For the mismatch case, make the two elements agree explicitly rather than by omission. For third-party scripts the correct pairing is CORS mode on both sides, which is also what makes resource timing and integrity available:

<link rel="preload" href="https://cdn.vendor.example/sdk.js" as="script" crossorigin="anonymous">
<script src="https://cdn.vendor.example/sdk.js" crossorigin="anonymous" defer></script>

For the unclaimed case, the fix is usually to delete the hint rather than to repair it. A preload is a commitment to fetch the payload immediately; if the payload is conditional, the commitment is wrong. Where the vendor is consent-gated, replace the preload with a connection hint, which costs no payload and does not expire:

<!-- Correct for a gated vendor: warm the socket, fetch nothing. -->
<link rel="preconnect" href="https://cdn.vendor.example" crossorigin>

Where the script is genuinely loaded on only some routes, emit the hint from the same place that decides to load it, rather than from a shared layout. A hint in a global template will always be wrong somewhere.

Verification: One Row, No Warning

Reload with the cache disabled and the filter still applied. Exactly one network row should appear for the URL, its initiator should read link, and the console should be silent. If the warning persists with a single row, the script is not loading on this page at all and the hint should not be here.

What a healthy preload looks like After the fix, the URL produces one network row whose initiator is the preload link, the script element reuses that response with no second request, and the console emits no warning. A note records that a correct preload is invisible: the only evidence of it working is the absence of a second row. network one row for the URL initiator: link no second request console silent no unused-preload warning nothing to investigate timing script starts earlier the point of the hint measurable on a throttled run A working preload has no positive signal — you confirm it by the absence of the two failure signatures.

Where the Hint Should Be Generated

Most recurring instances of this warning are a placement problem rather than an attribute problem. A hint hard-coded into a shared layout applies to every page that layout renders, while the script it refers to is conditional on a route, an experiment, or a consent decision — so the hint is correct on some pages and wasteful on all the others, and nobody notices because the console warning appears on pages nobody is debugging.

The durable fix is to generate hints from the same declaration that produces the script. If a vendor registry holds the source URL, the CORS mode and the integrity digest, it can render both the <link> and the <script> from one record, and they cannot disagree. It also means removing a vendor removes its hint, which is the other half of this problem: orphaned preloads for scripts that were deleted months ago are common and cost real bandwidth on every page load.

Where the vendor is conditional, make the hint conditional in the same expression. A route that loads a payment SDK should emit the payment SDK’s hint; a route that does not should emit neither. That sounds obvious stated plainly, and it is routinely violated because hints feel like configuration rather than like code with a lifecycle.

Common Pitfalls

  • Suppressing the warning by removing as. Dropping the attribute stops the browser classifying the resource, which does silence the message and also disables the optimisation entirely. The warning is doing its job; the hint is what needs fixing.

  • Adding crossorigin to the hint only. This converts an unclaimed preload into a double fetch, which is strictly worse: you now pay for the bytes twice instead of once.

  • Preloading a URL a tag manager rewrites. Custom-HTML templates frequently alter the element they inject, so the URL you preloaded is not the URL requested. Compare against the live element in the Elements panel, not the template source.

  • Treating the warning as a Lighthouse score problem. It does not appear in the score at all; it is a console message from the browser. Chasing it because a report mentioned it, rather than because bandwidth is being wasted, leads to the wrong fix.


Frequently Asked Questions

Is the warning ever safe to ignore?

Only when the preload is deliberately speculative and you have accepted the cost — for instance warming a resource you expect to need on most visits but not all. Even then, the bandwidth is spent on every visit where the claim never comes, so the trade needs to be one someone made on purpose rather than one nobody noticed.

In the common case, the warning is reporting real waste: bytes fetched at high priority, competing with resources the page did need, and then discarded. Treat it as a defect by default and require a justification to keep it, which is the opposite of how most teams handle it.

Why does the warning appear only sometimes?

Because the timer is measured from the load event and the claim races it. On a fast connection the consuming request may arrive inside the window and reuse the entry; on a slow one, or on a page where the script is injected after a user action, it arrives afterwards and the entry has gone. The same code therefore warns intermittently, which makes it easy to dismiss as noise.

Reproduce deliberately rather than waiting for it: throttle to Slow 4G, disable the cache, and reload several times. If the warning appears under those conditions it is present for a meaningful share of your real visitors, whose connections look far more like the throttled profile than like your desk.

Does the same matching rule apply to prefetch?

The matching is looser, because prefetch targets a future navigation rather than the current one and its result lands in the HTTP cache rather than the preload cache. That makes it far more forgiving of attribute differences — and correspondingly less useful for the current page, since a prefetched resource is fetched at the lowest priority and may not arrive in time to help.

The practical consequence is that swapping preload for prefetch to silence a warning is not a fix. It removes the message by removing the optimisation, and leaves you fetching a script at idle priority that the page needs immediately.


Up: Implementing Preload and Prefetch for Third-Party Scripts