You're on stackoverflow or MDN docs and found a code snippet that should help pagespeed. Just copy + paste it and hope it works!
That must have been what happened when analyzing the source code and its request/network waterfall of a recent case . The following code snippet was copied and pasted into their own webshop without giving it a lot of thought:
<link rel="preconnect" href="https://cdnjs.cloudflare.com/" crossorigin>
<link rel="dns-prefetch" href="https://cdnjs.cloudflare.com/">
Establish network connections early
The above HTML elements are called resource hints. These are meant to do some work in advance:
- The
preconnect
resource hint will do DNS lookup as well as TCP handshake and TLS negotiation; - The
dns-prefetch
resource hint only does a DNS lookup.
Such resource hints are convenient when you as a developer (or platform/CMS) know for sure that resources have to be fetched from a specific domain, but the browser has no chance of knowing this when parsing the first chunk(s) of HTML.
Why using both preconnect
and dns-prefetch
?
The dns-prefetch
is a fallback in case browsers don't support preconnect
. However, support is very good nowadays, only FireFox and (obviously) IE are behind when it comes to browser support for preconnect
.
The cost of resource hints
Usage often comes with nuances though, such as:
- how critical such resources hosted on other domains actually are;
- and if they could impact user experience metrics (such as Core Web Vitals) in a positive or negative way.
Do note that TCP and TLS involves some server resources, so a dns-prefetch
instead of a preconnect
is quite cheap to perform (a simple query-response to the DNS server, that is cached in the browser for a short amount of time).
And as in this case you could ask yourself how critical the resources and thus the related resource hints in this situation were, this wasn't what stood out.
Analyzing the performance issue
Knowing that a dns-prefetch
will only do the DNS lookup and a preconnect
will do additional work, it's quite easy to spot issues when looking at a Webpagetest.org waterfall:
We're seeing two requests:
- request number 1 is the initial document request (with a high TTFB, but that's another issue), responding with HTML which then contains references to other resources;
- request number 79 is the first request to the "flag-icon-css" related resource hosted on cdnjs.cloudflare.com.
We can clearly see the dns-prefetch
resource hint paying off as the DNS lookup is performed ahead of the moment where the browser got to point of discovering the reference to request number 79.
preconnect
work is missing in action
However, we are missing some browser-work within request number 79. Didn't we do a preconnect
in the first place, moving the connect + ssl work forward as well? That's where we can spot an issue and draw the conclusion that something isn't working, as we learned before that preconnect
is supported in Google Chrome (used to do this webpagetest analysis).
When looking at the code snippet, we see that the preconnect
resource hint got a crossorigin attribute attached to it. And this is breaking the expected behaviour. As a result, the browser only used the DNS prefetch for the required type of resources and the website did not get any benefit out of the preconnect
.
What went wrong with preconnect
and crossorigin?
Nothing actually, as long as it's used for the right purpose. And that's where things south. My guess it that the developers copied their code from MDN dns-prefetch docs and just changed https://fonts.googleapis.com/ to https://cdnjs.cloudflare.com/ and called it a day. Without knowing what crossorigin did.
What does "crossorigin" exactly do?
This is the literal question I got asked on LinkedIn. The short answer version is:
Fonts (just like JS) are considered restricted resources and are loaded in anonymous mode. So when downloaded from a different domain ( = crossorigin ) than the domain someone is visiting, you would have to let browsers know via additional (meta)information that it's okay/safe to actually fetch those resources from a different domain.
In such cases you should set the crossorigin attribute with the preconnect
hint as require its own type of connection, as this additional meta-information has to be send along. If you fetch both fonts and for example images from the same CDN, then you would need to preconnect
resource hints, as the crossorigin will be used for fonts and the other one will be used for resources such as images and stylesheets:
<link rel="preconnect" href="https://cdnjs.cloudflare.com/" crossorigin>
<link rel="preconnect" href="https://cdnjs.cloudflare.com/">
Why isn't crossorigin needed for dns-prefetch
?
You won't be seeing crossorigin attributes with dns-prefetch
resource hint. The reason is that a dns-prefetch
is just the DNS lookup. This didn't involve a TLS negotiation yet and nothing is being sent back and forth. As a result, a dns-prefetch
resource can be shared across different requests and the crossorigin attribute isn't needed.
What about crossorigin and preload
?
Preload is used for specific individual files. When you want to fetch font-files, such as woff or woff2, you would need to use the crossorigin attribute as well when using the preload
resoure hint.
Resource hint bonus insights
A heads-up: don't try to be a smartass and combine dns-prefetch
and preconnect
in one link-element. Andy Davies discovered that actually nothing works in WebKit (Safari).
Some other resources:
- Stackoverflow Q&A about
dns-prefetch
versuspreconnect;
dns-prefetch
browser support andpreconnect
browser support;- MDN on Cross Origin Resource Sharing.