EXPEDIA GROUP TECHNOLOGY—ENGINEERING

Dns-prefetch & Preconnect: 7 Tips, Tricks and Pitfalls

Mastering the not so obvious parts of dns-prefetch & preconnect

Jorge Barco
Expedia Group Technology

--

Big 4 story library full of shelves with books on them.
Photo by Niklas Ohlrogge on Unsplash

The first time I worked with resource hints things just didn’t work as expected. Real user monitoring (RUM) metrics were not showing any of the promised improvements when fetching resources. Information was all over the place and some of the trickiest edge cases were hidden in the high volume of articles available.

In this article, I want to put the most important concepts in one place, so that if you are in the same situation, you have at least one place to start with dns-prefetch and preconnect. There are more resource hints such as preload, but I won’t cover them in this article.

If you don’t know what preconnect or dns-prefetch are, here is a short introduction. Feel free to skip it if you already know what they are and jump to Tips, tricks and pitfalls section. At the bottom you can find an extensive list of references supporting the main topics described in the article.

Resource hints

When the browser parses your HTML, it fetches all the resources such as fonts, stylesheets and scripts if they are not already cached. That means that the browser has to reach the line of code where your resource is declared in your HTML, resolve the DNS of the resource and establish a connection with that host to download the asset.

Image displaying a browser resource waterfall view. When browser hints are not used, resources are loaded as they are discovered and for each resource, the browser spends time connecting to the host, then downloading and processing the asset. It performs the entire process for each asset sequentially.
Waterfall view

As the previous image shows, there is a sequential order followed by the browser to download resources.

Preconnect

What if we could tell the browser that there is going to be a connection with a specific host in the short future? This is what preconnect is all about:

The preconnect link relation type is used to indicate an origin that will be used to fetch required resources. Initiating an early connection, which includes the DNS lookup, TCP handshake, and optional TLS negotiation, allows the user agent to mask the high latency costs of establishing a connection — Source: W3C.org

If we know some resources are going to be fetched from specific hosts but we don't know exactly what resources we will download, we can save some time by establishing a connection using preconnect. We can either specify it in the document markup or via the HTTP Link header:

  • Markup: <link rel="preconnect" href="https://www.vrbo.com">
  • Link header: Link: <https://www.vrbo.com>; rel=preconnect

dns-prefetch

We might be interested in doing the dns lookup only. In this case we can use dns-prefetch.

The dns-prefetch link relation type is used to indicate an origin that will be used to fetch required resources, and that the user agent SHOULD resolve as early as possible. — Source: W3C.org

As with preconnect, dns-prefetch can either be specified in the document markup or via the HTTP Link header:

  • Markup: <link rel="dns-prefetch" href="//example.com">
  • Link header: Link: <https://www.vrbo.com>; rel=dns-prefetch

Main difference between dns-prefetch and preconnect

This is the simplest way of looking at these two:

  • dns-prefetch: DNS lookup
  • preconnect: DNS lookup + TCP handshake + TLS negotiation

dns-prefetch just warms up the local cache and it doesn’t tie up any resources. However, preconnect will maintain a connection with the specified origin. As this is a costly operation, we should only use preconnect for origins we will surely connect to.

Tips, tricks and pitfalls

In this section we’ll cover the not so obvious parts related to dns-prefetch and preconnect.

1. Auto dns-prefetch for hyperlinks

Some browsers do a dns-prefetch for the hyperlinks displayed in your HTML, so that when the user clicks on it there is no additional latency added to the DNS resolution. An important point to take into account is that Chrome does not dns-prefetch hyperlinks in HTTPS pages by default:

By default, Chromium does not prefetch host names in hyperlinks that appear in HTTPS pages. This restriction helps prevent an eavesdropper from inferring the host names of hyperlinks that appear in HTTPS pages based on DNS prefetch traffic. The one exception is that Chromium may periodically re-resolve the domain of the HTTPS page itself. — Source: chromium.org

As stated in this MDN entry, you can enable/disable this functionality in Chrome and Firefox by adding a X-DNS-Prefetch-Control header. It can also be set up through a meta tag:<meta http-equiv="x-dns-prefetch-control" content="on">.

This option should only be enabled if we know for sure it will not interfere with the user event tracking system. Otherwise users might be registered for visiting links they have not clicked on.

2. Use dns-prefetch as a fallback for preconnect

If we check on caniuse.com, we can see that more browsers support dns-prefetch than preconnect:

The result of searching dns-prefetch on caniuse.com. The number of users whose browser supports dns-prefetch is 80.37%.
Browsers supporting dns-prefetch — caniuse.com
The image shows the result of searching preconnect on caniuse.com. The number of users whose browser supports preconnect is 89.46%, or almost ten percent more users whose browsers support preconnect.
Browsers supporting preconnect- caniuse.com

preconnect links can be declared followed by a dns-prefetch link so that if the first one is not supported, time can be saved for the DNS resolution of the origin:

<link rel="preconnect" href="https://example1.com"> 
<link rel="preconnect" href="https://example2.com">
<link rel="preconnect" href="https://example3.com">
<link rel “dns-prefetch” href=”https://example1.com">
<link rel=”dns-prefetch” href=”https://example2.com">
<link rel=”dns-prefetch” href=”https://example3.com">

3. Cross-Origin Resource Sharing (CORS)

If our resource is going to be fetched through a CORS connection using anonymous mode, we need to add the attribute crossorigin to the link tag:

<link rel="preconnect" href="https://cdn.vrbo.com" crossorigin>

As stated in MDN article, CORS is used to enable cross-site HTTP requests for:

  • XMLHttpRequest / Fetch APIs in a cross-site manner.
  • Web Fonts (for cross-domain font usage in @font-face within CSS).
  • WebGL textures.
  • Images/video frames drawn using drawImage().
  • CSS Shapes from images.

One of the most common mistakes is to set a CORS connection by using the attribute crossorigin and then fetching a non-CORS asset type such as a script.

Ilya Grigorik explains in his article how this simply won’t work as CORS and non-CORSresources are downloaded using separate connections. There is more information about this topic on this great article written by Michael Crenshaw.

Sometimes, especially when we fetch resources from a CDN, we fetch CORS and non-CORS assets.

Image displaying a resource waterfall view where two assets, one CORS and one non-CORS, are from the same origin. This image shows the non-optimized situation where we haven’t used appropriate browser hints for both connections. Here, the browser doesn’t start the CORS and non-CORS connections required to download the resources until the asset links are discovered by the browser, so the connections are established one at a time.

As the image above shows, we are establishing two different connections to cdn.vrbo.com. We can set up two preconnect hints, one of them with a crossorigin attribute:

<link rel="preconnect" href="https://cdn.vrbo.com">
<link rel="preconnect" href="https://cdn.vrbo.com" crossorigin>

We can see that following this approach, we will set up these two connections at the same time:

Image displaying a resource waterfall view where two assets, one CORS and one non-CORS, are from the same origin. This image shows the optimized situation where we have used appropriate browser hints for both connections. Here, the browser starts both connections required to download the resources when the hints are encountered, and so the connections are started in parallel. The total time to load all assets is shorter.

4. The browser closes unused connections

On this Google Developers article as well as on Milica Mihajlija’s Google Developers entry, we can see that unused connections are closed after ten seconds of inactivity. That means that if it takes more than ten seconds to find and start fetching a resource, all work done to preconnect with the origin could be wasted.

5. Number of connections

Opening a connection is an expensive operation. Though he doesn’t offer the data in this article to support his guidance, we can keep in mind the advice of Ivan Akulov. On his blog, he recommends to only use preconnect when we’re certain we’ll use the connection, with no more than 4-6 domains. And we can still use dns-prefetch for other connections.

6. Browsers can ignore resource hints

Only preload is mandatory for browsers at this time. All other resource hints are still in the working draft stage and a browser can ignore them if it chooses to do so.

The browser could choose not to set up a new connection if there are already too many connections open. You shouldn’t expect a given preconnect to work just because you asked for it.

7. Test your hypothesis, you can make things worse

As we explained at the beginning of this article, adding resource hints might help to reduce the time needed to download resources from hosts. The best benefit of resource hints is that by the time the browser discovers a resource, a connection is already in place to fetch it.

However, as we remove connection times when some resources are found, we could end up changing the order in which resources are fetched and processed. In some cases that could affect some of your Core Web Vitals metrics, causing performance degradation.

Always try to understand how your resources are loaded and test your hypothesis based on that analysis.

A great tool to test your loading flow is WebPageTest. Andy Davies explains in his article how to inject scripts using WebPageTest you might use for your testing. Additionally, you can run your own instance of WebPageTest locally. If you are interested in this approach, you might find Francis John’s blogpost useful.

Summary

In this article we have covered 7 of the most relevant aspects to take into account when using dns-prefetch and preconnect resource hints. Working with these browser resource hints can be confusing and frustrating. I hope this article has provided a better understanding of CORS connections and why dns-prefetch is used as a fallback for preconnect. As always, remember to test your hypothesis to ensure your changes do not cause a performance degradation.

References

Graphics in this post were constructed by the author. Screenshots taken from caniuse.com are under under the CC BY 4.0 license.

--

--