Since Google announced they would be introducing a page ranking change based on Core Web Vitals, page performance is really having its heyday in the SEO world. Lighthouse reports are no longer just for the developers! (We already knew that, though).
But do you really understand what Lighthouse performance audits are asking you to do? In one case in particular, Google doesn’t even seem to know. Google’s own web.dev resource for “preconnect to required origins” mentions using “preconnect” or “dns-prefetch”, but recommends at the end of the page to use “preload” instead. So what are these and how can you know which you should be using and when?
What are resource hints?
”preconnect”, “dns-prefetch”, and “preload” are all relationship types of resource links — the “rel” attribute in a link tag. According to the W3C, a resource hint link uses one of these relationships to indicate an origin or resource that should be connected to, or fetched, by the user agent. That’s a pretty vague description, so let’s break that down a bit further.
What must happen in order to connect to a resource? For resources on other domains, or cross-origin resources, we can get a good sense of the steps involved by referring to the Network requests in Chrome Dev Tools.
When selecting a resource on another domain (in this case gtm.js from googletagmanager.com) and reviewing the Timing tab, you can see the Connection Start requires a DNS lookup, initial connection, and SSL. These actions happen in that order as the DNS lookup, TCP handshake, and SSL/TSL handshake. After the connection has been established, the browser/client can send a request to the server and receive a response, which is indicated in the Request/Response timings. Learn more about all of these timings.
Origin vs Resource
When thinking about these relationships, we must consider what we’re connecting to. Origin refers to the scheme, hostname, and port of a URL, so it’s referring specifically to connecting to other domains. “preconnect” and “dns-prefetch” are used for connecting to other origins, which is why those are listed in the “preconnect to required origins” guide from Google. However, at the end of that guide it is recommended that we use “preload” in most cases. “preload” is able to do more than just establish early connections with domains. Let’s look a little deeper into what each type does.
Somewhat self-explanatory, “dns-prefetch” helps resolve domain names before the resource is requested. Therefore, this type will start the DNS lookup portion of the connection before the time of the request.
“dns-prefetch” is not supported on Opera Mini, and is not supported via HTTPS on Firefox. If your site is using HTTPS (as it should be), then you will need another option for Firefox users.
“preconnect” has the browser handle all of the DNS lookup, TCP handshake, and TLS handshake (all of the Connection Start timings) before the request to the server.
Unfortunately, “preconnect” is supported on even fewer browsers than “dns-prefetch.”
The “preload” relationship type is the one recommended by Google in most cases, because it is the most comprehensive. This resource hint tells the browser to prioritize downloading and then caching the resource to have ready at the time of the request.
In order to use “preload,” you must know the type of resource you are requesting and add an additional attribute of “as” to the link tag. Learn more about the different types of content that can be preloaded and know that some browsers limit the types of content that can be preloaded, while many browsers simply do not support “preload” at all.
When should I use resource hints?
Because we must use the “as” attribute on the “preload” hint, we are limited to only using it on resources when we know the content type and that content type is an acceptable value for “as.” That is why this works well for fixing critical request chains caused by first-party resources, like stylesheets, which is explained in the “preload key requests” web.dev resource. Let’s see it in action on my personal website (because it is old and could use a lot of help).
On my site, I link to a styles.css file from my HTML file. The stylesheet then imports a Google font, which then requests another resource. Because the index.html has to be loaded to then call the styles.css file that imports the Google font, the Google font resource request is chained three levels. Here’s how it appears in the Lighthouse reports.
<link rel="preload" href="css/icofont.css" as="style"> <link rel="preload" href="css/styles.css" as="style">
By adding two preload links at the top of my <head> to point to each .css file, my critical request chain now looks like this.
Bonus, preloading these resources also reduced render-blocking!
Before adding preload to the styles
After adding preload to the styles
That was a savings of 1.5 seconds!
What should we do about tag managers?
Website tracking, like Google Analytics, is a common resource that isn’t shown in my Lighthouse reports. My website uses Google Analytics (GA) via Google Tag Manager (GTM), so my HTML has the GTM script tag. By design, tag managers create request chains. The HTML must load the GTM script, which can then load the analytics.js script. So how should we speed up connection to GTM and/or GA, since they are third-party resources?
To speed up the initial connections, I can add a link tag with the href attribute pointing to “https://www.googletagmanager.com” and a rel attribute of “preconnect” or “dns-prefetch”. Let’s see how that changes our Timings for that network call then.
<link rel="dns-prefetch" href="https://www.googletagmanager.com/">
If you look back at our initial Timings screenshot, without any resource hints, the Connection Start had no timings other than being stalled for 123ms, because the initial connection isn’t started until the request. Now using “dns-prefetch”, we can see that the Initial Connections are made in what would have been entirely “stalling” time. Our total duration for this resource is 155.75ms, which is down from 286.71ms previously.
When adding the rel attribute with “preconnect,” it didn’t appear as though any initial connection start was happening in the timings, like the very first timings screen. Why? The likely culprit is CORS (Cross-Origin Resource Sharing). We didn’t run into this problem with “dns-prefetch” because it isn’t actually fetching the resource, only looking up the DNS. Since resource hints aren’t sending a full request/response with headers, we can add the “crossorigin” attribute instead. Now let’s see how our timings appear using rel=”preconnect” with the crossorigin attribute.
<link rel="preconnect" crossorigin href="https://www.googletagmanager.com/">
While the total duration on this is slightly higher than our “dns-prefetch” (although that could be tied to other variables changing, causing the initial queuing to delay longer), all of the Request/Response timings are improved, because “preconnect” handles more of that initial connection prior to the request than “dns-prefetch” does. You can see that the Request Sent hit happens further into the orange and purple waterfalls of the Connection Start timings than it did for the “dns-prefetch.”
Now what about the recommended resource hint, “preload”? I saved this one for last because, like the “crossorigin” attribute, it requires additional information to send as a replacement for those request headers. Remember, we must use the “as” attribute for this link relation type. With that in mind, we can’t connect to just “https://www.googletagmanager.com”; we need to find the exact resource on that domain. When we check the GTM code snippet, we can see reference to “https://www.googletagmanager.com/gtm.js,” so let’s try that resource link with an “as” attribute value of “script” and maintain our CORS attribute too.
<link rel="preload" crossorigin href="https://www.googletagmanager.com/gtm.js" as="script">
See that failed network call? That’s our preload. So what happened here? The gtm.js network call is second in our waterfall, and another appears further down (as it has before) when the actual request is made. That second gtm.js network call actually contains the answer to our “preload” failure; we need to include our GTM container ID on the URL.
<link rel="preload" crossorigin href="https://www.googletagmanager.com/gtm.js?id=GTM-K6D9C4" as="script">
Okay, so now we have the preload network call with a time duration of 104ms and then the request from the GTM code snippet further in the waterfall with a time of 86ms. This doesn’t seem like much of an improvement.
From this testing, I would suggest using “dns-prefetch” where you can for third-party resources like GTM. It has the most browser support and is the simplest to implement, since it does not require sending data about CORS or content types. For your owned resources, like stylesheets, “preload” makes a real impact on render-blocking resources and reducing critical request chains.