How to check preload and preconnect hints

Fold the markup onto one line and pull the hint tags out of it with grep, then read the response headers for the same hints. A page can declare preload and preconnect in either place, and every preload needs an as attribute or the browser fetches the file a second time.

Why check this

Resource hints fail silently. A preload that points at a file the build renamed downloads the old file on every page load, renders nothing, and breaks no assertion, so a functional suite never sees it. Run this on the performance pass before release, and again after any change to the document head or to the CDN configuration. The count is also a regression target: a template that gained four preconnect tags in one release opens four TLS handshakes the page may never use.

Prerequisites

Steps

  1. Step 1.

    List every hint tag in the markup.

    curl -s https://www.cloudflare.com/ | tr '\n' ' ' | tr -s ' ' | grep -o '<link[^>]*rel="[^"]*pre[^"]*"[^>]*>'
    
    <link rel="preconnect" href="https://imagedelivery.net" crossorigin>
    <link rel="preconnect" href="https://ot.www.cloudflare.com" crossorigin>
    <link rel="preload" href="/icons.svg" as="image" type="image/svg+xml">
    <link rel="preload" href="/fonts/Kunst%20Grotesk%20Regular.woff2" as="font" type="font/woff2" crossorigin>
    <link rel="preload" href="/fonts/Kunst%20Grotesk%20Medium.woff2" as="font" type="font/woff2" crossorigin>

    The two tr calls fold the document onto a single line and squeeze the runs of spaces. Without them a link tag written across several lines is invisible to the pattern.

  2. Step 2.

    Count the hints by keyword.

    curl -s https://www.cloudflare.com/ | tr '\n' ' ' | tr -s ' ' | grep -o '<link[^>]*rel="[^"]*pre[^"]*"[^>]*>' | grep -o 'rel="[^"]*"' | sort | uniq -c
    
          2 rel="preconnect"
        3 rel="preload"

    The same pattern covers prefetch, dns-prefetch and modulepreload. A keyword missing from this list is a keyword the page does not use.

  3. Step 3.

    Read the response headers, because hints also travel as Link.

    curl -s -D - -o /dev/null https://www.cloudflare.com/ | grep -i -E '^(link|HTTP)'
    
    HTTP/2 103
    link: </fonts/Kunst%20Grotesk%20Regular.woff2>; as=font; crossorigin; rel=preload; type=font/woff2, …, </static/hero-poster.avif>; as=image; fetchpriority=high; rel=preload; type=image/avif
    HTTP/2 200
    link: <https://www.cloudflare.com/.well-known/agents.json>; rel="api-catalog", …, </static/hero-poster.avif>; rel=preload; as=image; type="image/avif"; fetchpriority=high

    Two status lines arrive for one request. The 103 is an Early Hints response, sent before the server has the page, and it carries the hints on their own. Compare the header list with step 1: /static/hero-poster.avif is preloaded by the header and appears in no tag, while /icons.svg appears in a tag and in no header.

  4. Step 4.

    Confirm every preload declares a destination.

    curl -s https://www.cloudflare.com/ | tr '\n' ' ' | tr -s ' ' | grep -o '<link[^>]*rel="preload"[^>]*>' | grep -vc 'as='
    
    0

    Zero is the passing result. Any other number is the count of hints that name no destination.

  5. Step 5.

    Count how often each preloaded URL appears in the document.

    curl -s https://www.cloudflare.com/ | grep -o -E 'icons\.svg|Kunst%20Grotesk%20Regular\.woff2' | sort | uniq -c
    
          1 Kunst%20Grotesk%20Regular.woff2
       75 icons.svg

    icons.svg is named 75 times, so the markup itself consumes it. The font is named once, by the hint and by nothing else, which means its only consumer can be a stylesheet or a script. That is where an unused preload hides.

How to read the result

| What you see | What it means | What to do | | --- | --- | --- | | rel="preload" with as and a matching type | The hint is complete | Nothing. Confirm the file still exists. | | rel="preload" with no as | The browser cannot match the hint to the later request | Add as. Until then the file arrives twice. | | A preloaded URL counted once in step 5 | Only the hint mentions it | Open the page in Chrome and read the console warning before keeping the hint. | | rel="preconnect" to your own origin | No effect | Remove it. The connection carrying the HTML is already open. | | Hints in the Link header and none in the markup | A proxy or CDN injects them | Test the CDN, not the template. The origin response will not show them. | | A 103 status line before the 200 | Early Hints are enabled | Read the 103 list, not only the 200 list. |

Common mistakes

Sign: A preloaded font or script is downloaded twice on one page load.Cause: The preload and the real request do not match. A missing or wrong as attribute leaves the browser without a destination to match against, and a font preload without crossorigin uses a different CORS mode than the eventual font request. MDN requires crossorigin on font and fetch preloads for that reason.
Sign: Chrome logs that a resource was preloaded but not used within a few seconds.Cause: The hint survived a refactor that removed the resource, or it points at a build artefact whose hash changed. The page still renders, so no functional test fails. Step 5 finds the same thing from the command line: a preloaded URL that the document mentions exactly once.
Sign: grep on the markup returns nothing, yet the page preloads two fonts.Cause: The link tag is written across several lines, so a line based pattern never matches it. developer.mozilla.org formats its font preloads that way, and a one line grep reports zero hints on a page that has two. The tr pair in step 1 removes the problem.
Sign: A preconnect to a third party origin makes no measurable difference.Cause: Preconnect only helps a cross origin request that has not started yet, and a page with many third party origins gains nothing from preconnecting all of them. MDN recommends dns-prefetch for the rest and preconnect for the few connections that hold up rendering.

What to check next

FAQ

How to check if preconnect is working?

Compare two loads of the same page in the Network panel with the cache disabled, and read the Connection Start timings for the hinted origin. A working preconnect moves the DNS and TLS phases off the critical request, so the first request to that origin shows those phases already finished.

How to check if preload is used?

Run step 5. A preloaded URL that the document mentions once is consumed by CSS or JavaScript, or by nothing at all. Chrome confirms the second case in the console within a few seconds of load, naming the URL that was preloaded and never requested.

Do hints in the Link header work the same as tags?

Yes for preload and preconnect. The header version arrives earlier, before the parser reaches the head, and a 103 Early Hints response delivers it earlier still. The trade is that the CDN sets the header, so the template does not show it.

Does a preload change the request priority?

It raises the priority of a fetch the browser would otherwise start later. It does not make a slow server faster. Pair it with fetchpriority when two preloads compete, as www.cloudflare.com does on its hero image.

Is modulepreload the same as preload with as=script?

No. modulepreload also parses the module and fetches its static imports, so one hint covers the dependency graph. preload with an as of script fetches one file and stops.

Verified

Verified by Maks Vernycurl 8.21.0

Each output block is what the command above it printed on that date, on the host named in the step. Figures read from a live site move between runs. Compare the shape of the answer rather than the digits, and see the methodology for how a page is re-verified.

basic5 minpublished updated Maks Verny