25.04.24
8 min read
Today, I read an article by Willen Wigman from Hyvä discussing a new browser feature known as "speculation rules." This feature prerenders pages as a background browser task. In simple terms, the browser attempts to predict the user's next click and preloads that page. Therefore, when the user actually clicks the link, the page is already loaded and displays instantly. Currently, about 75% of all common browsers support this feature.
As mentioned, the browser predicts which site the user might visit next and preloads that page. If the browser's guess is correct, the page loads almost instantaneously.
To use the speculation rules API, you need to add a small script to the end of your site:
1<script> 2if (HTMLScriptElement.supports && 3 HTMLScriptElement.supports('speculationrules')) { 4 const specScript = document.createElement('script'); 5 specScript.type = 'speculationrules'; 6 specRules = { 7 "prerender": [{ 8 "where": { 9 "or": [10 {"href_matches": "/"},11 {"href_matches": "/*.html"},12 {"href_matches": "/blog*"},13 {"href_matches": "/*-feature-matrix/"},14 {"href_matches": "/(imprint|privacy)/"}15 ],16 },17 "eagerness": "moderate"18 }]19 };20 specScript.textContent = JSON.stringify(specRules);21 document.body.append(specScript);22}23</script>
Adjust the prerender rules to fit your specific needs. Adding all level 0 categories for a Magento 2 store is a smart approach. If you're not using category slug URLs for your products, you should consider different rules. Theoretically, you could use '*' as a wildcard and add a few exclusions for checkout and cart pages.
The "eagerness" value determines how the prerendering operates. If set to "immediate," the first 10 URLs will be prerendered. This may be suitable for small sites with just a few URLs. Setting the eagerness to "moderate" allows for dynamic functionality, where the browser preloads URLs that have been hovered over by the user for at least 200ms. This approach is especially effective for online shops.
Since each user with the preload pages feature generates multiple requests per page, overall hits against the webserver increase. This is generally manageable with solutions like Varnish. Although sites are loaded even if a user might not visit them, they will be cached in the FPC for faster load times for the next user. Be cautious with this feature on high-traffic sites. Know your setup and keep an eye on your monitoring during the initial days.
Although prerendering offers significant performance optimization, we currently see a few drawbacks.
As mentioned, the feature is not yet available in all modern browsers—~73% support sounds promising, but it's still too low for a significant impact. Another issue is that common ad-blocking browser extensions, like uBlock Origin, forcibly disable the Preload Pages Feature.
Not to pass judgment, but using this feature generally increases the number of requests, which aren't necessary initially and consume resources. As with any resources, waste isn't ideal, so code responsibly.
Actually using the feature in production gave more insights. We actually came up with a ready-to-use snippet
1<script> 2if (HTMLScriptElement.supports && 3 HTMLScriptElement.supports('speculationrules')) { 4 const specScript = document.createElement('script'); 5 specScript.type = 'speculationrules'; 6 // Correctly declare specRules as a constant within the block scope 7 const specRules = { 8 "prerender": [{ 9 "source": "document",10 "where": {11 "and": [12 {13 "href_matches": "/*"14 },15 {"not": {"href_matches": "/*(customer|login|logout|auth|cart|checkout|search|download|redirect|rewrite|store|productalert)/*"}},16 {"not": {"href_matches": "*.pdf"}},17 {"not": {"selector_matches": ".do-not-prerender"}},18 {"not": {"selector_matches": "[rel=nofollow]"}},19 {"not": {"selector_matches": "[target=_blank]"}},20 {"not": {"selector_matches": "[target=_parent]"}},21 {"not": {"selector_matches": "[target=_top]"}}22 ]23 },24 "eagerness": "moderate"25 }]26 };27 specScript.textContent = JSON.stringify(specRules);28 document.body.append(specScript);29}30</script>
This snippet makes sure to avoid some common miss-uses of speculation rules. As you can imagine, you don't want any authentication sites being unintentionally loaded. Same logic counts for language switchers or rewrites. Although most of these functionalities don't use actual GET parameter, it is safer to add them to the ignore list.
We've been running speculative rules for a few days now on a production site. We use RUMVision to monitor the actual user experience on the site and annotated the change in RUMVision. The results over only a few days are impressive. As you can see in the image below, every metric improved by a significant amount.
If you want to deepen your understanding of speculative features, particularly the speculative loading API, I have hand-picked a few resources for you to explore.