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
- curl 7.0 or later. No HTTP/2 support is needed for this check. See the curl manual.
grepandtr. Both ship with Git Bash on Windows.- The MDN reference on rel=preload and the MDN reference on rel=preconnect for what each keyword promises.
- The page URL and the name of the font, script or image that should arrive first.
Steps
- 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
trcalls 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. - 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 -c2 rel="preconnect" 3 rel="preload"The same pattern covers
prefetch,dns-prefetchandmodulepreload. A keyword missing from this list is a keyword the page does not use. - 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=highTwo status lines arrive for one request. The
103is 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.avifis preloaded by the header and appears in no tag, while/icons.svgappears in a tag and in no header. - Step 4.
Confirm every
preloaddeclares a destination.curl -s https://www.cloudflare.com/ | tr '\n' ' ' | tr -s ' ' | grep -o '<link[^>]*rel="preload"[^>]*>' | grep -vc 'as='0Zero is the passing result. Any other number is the count of hints that name no destination.
- 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 -c1 Kunst%20Grotesk%20Regular.woff2 75 icons.svgicons.svgis 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
What to check next
- How to check render blocking resources: the head that carries these hints also carries the stylesheets that hold up the first paint.
- How to check LCP of a page: a preload on the hero image is measured here, not in the markup.
- How to check DNS lookup time: the number a
dns-prefetchhint is meant to remove. - How to check number of requests on a page: a hint that nothing uses still costs one request.
- How to check HTTP response headers with curl: the wider read of the header block that step 3 samples.
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.
Related on this site
basic5 minpublished updated Maks Verny