“Time cannot be bought. But time can be saved.”
This quote may be too deep for a web development post, but it exemplifies well what performance means to User Experience (UX). The most important performance metrics revolve around the first fractions of second between the request of your application and the user interacting with it.
People have built profitable businesses shaving off those little milliseconds. Others have spent considerable amount of time studying the correlation between that loading interval and direct conversions.
It’s not a rumor the position your website shows in Google’s (and other search engines as well) Search Results Page (SERP) is influenced by performance.
Performance not only determines a big portion of how pleasant the UX will be, but also if you’ll even get to pitch your product to that specific user. It also intertwines with accessibility, allowing limited connections to have a seamless experience is as much a business as it is an ethical concern.
Besides optimizing our own code for performance, there are a couple of low hanging fruits which allow browsers to help us on the endeavor of providing fast and smooth navigation.
DNS-Prefetch
Prefetching the DNS means advising your browser a connection to that server will be sought. Therefore, the browser can antecipate the DNS lookup. This decreases the latency between server connections, which are often cause of poor perceived performance in websites.
<link rel="dns-prefetch" href="https://fonts.gstatic.com/" />
⚠️ Needless to say it should only be used for third-party requests. Your own domain will have already been resolved by the time this hint is read.
Preconnect
Adding rel="preconnect"
to your <link>
will advise the browser you intend to make request towards the domain in its href
and you would like the browser to establish a connection with that server as soon as possible. While prefetch
only accelerates the DNS lookup, preconnect
has a more significant impact:
- DNS lookup (just like
prefetch
) - TCP connection establishment
- TLS handshake (for
https
requests)
It’s worth noting that establishing multiple preconnections may and will cause a bottleneck, degrading performance instead of improving it. Therefore preconnect is only recommended to use on the most important resource. Prefetch, on the other hand, can be used more freely and combining the two can only help.
<link rel="dns-prefetch" href="https://fonts.gstatic.com/" />
<link rel="preconnect" href="https://fonts.gstatic.com/" />
Blocking Chained Requests
Starting connections early on can only go so far. Some resources depend on others to be useful, like a font depends on a stylesheet. In the above example (fonts.gstatic
domain), connection starts as early as possible. But it still can present a considerable risk and bottleneck to performance.
Most fonts are imported through passing a source in @fontface
rule, inside your CSS stylesheet. See the problem? If your CSS is an external resource, it needs to be requested, loaded, and parsed in order to even start fetching your font.
And the issue goes on!
To save users from poorly implemented interfaces where resources are fetched recklessly, the browser does not fetch the font as soon as it finds the @fontface
rule (which is usually on top). No, the browser waits until at least one element in the DOM requires that font. And then, it blocks the entire rendering. Fetches the font, loads the font, parses the font, applies the styles. Now the browser can continue to build the rest of the DOM.
Luckily, there is a way of telling the browser beforehand such a disruptive resource will be required.
Preload
This hint declares a fetch request within the HTML. In the Web font example above, the font resource could be linked to directly to the HTML. Eliminating the request chain.
⚠️ Limit the resources to be preloaded to a bare minimum to avoid performance bottlenecks.
<link
rel="preload"
href="fonts/webfont.woff2"
as="font"
type="font/woff2"
crossorigin
/>
Preload requires a little bit more configuration than preconnect
and dns-prefetch
. For a resource to be properly loaded, it needs to have a MIME/TYPE
, and the link
tag accepts another attribute for indicating a request will be cross-origin.
Two important things to remember:
- Font requests are always cross-origin, regardless where they’re hosted
- For a cross-origin request to work, you also need to have CORS already configured
It is also possible to pass a media
attribute for responsive preloading.
<link rel="preload" href="small.png" as="image" media="(max-width: 600px)" />
<link rel="preload" href="big.png" as="image" media="(min-width: 601px)" />
Managing on the server-side
You can pass these and other <link>
attributes as value of a HTTP Response Header as well. The Link
field exists precisely for this and can be passed multiple values separated by a semi-colon.
Link: <https://one.example.com>; rel="dns-prefetch", <https://two.example.com>; rel="dns-prefetch", <https://three.example.com>; rel="preconnect"
Which other low hanging fruits are there in web performance that you would also recommend?
Top comments (2)
Super insightful, thanks a lot! What’s your rule of thumb for the amount of preconned one can make?
I haven't seen a recommended limit on any docs yet... And technically that's the cheapest hint.
But they should be mainly the resources needed for the initial load.
More than that can cause some competition and end up increasing Max Potential Input Delay I think.