Joan León PRO
⚡️ Web Performance Consultant | Speaker | Staff Frontend Engineer at @AdevintaSpain | @GoogleDevExpert in #WebPerf | @cloudinary Ambassador
65.5% of origins (↑ 0.2%) had good LCP
78.2% of origins (~ 0.0%) had good CLS
85.9% of origins (↑ 0.2%) had good INP
51.2% of origins (↑ 0.3%) had good LCP, CLS and INP
Lab
Field
Loading
Load page
Prerender
Prerender
Load page
Load page
LCP
Load page
Prerender
<script type="speculationrules">
{
"prerender": [
{
"urls": ["product-list-page.html"]
}
]
}
</script>
home.html
Load page
Prerender
<script type="speculationrules">
{
"prerender": [
{
"urls": ["product-detail-page.html"]
}
]
}
</script>
product-list-page.html
Load page
Prerender
<script type="speculationrules">
{
"prerender": [
{
"urls": ["product-detail-page.html"],
"eagerness": "..."
}
]
}
</script>
product-list-page.html
<script type="speculationrules">
{
"prerender": [
{
"source": "document",
"where": {
"selector_matches":
"section:nth-child(3) > ul > li:nth-child(1) a"
},
"eagerness": "immediate"
}
]
}
</script>
if (HTMLScriptElement.supports?.('speculationrules')) {
const url = document.querySelectorAll('section:nth-child(3) > ul > li:nth-child(1) a')[0].href;
const specScript = document.createElement('script');
specScript.type = 'speculationrules';
const specRules = {
prerender: [{
"source": "list",
"urls": [url],
"eagerness": "immediate"
}]
};
specScript.textContent = JSON.stringify(specRules);
document.body.append(specScript);
} else {
console.warn('Speculation Rules API is not supported in this browser');
}
if (HTMLScriptElement.supports?.('speculationrules')) {
setTimeout(() => {
const url = document.querySelectorAll('section:nth-child(3) > ul > li:nth-child(1) a')[0].href;
const specScript = document.createElement('script');
specScript.type = 'speculationrules';
const specRules = {
prerender: [{
"source": "list",
"urls": [url],
"eagerness": "immediate"
}]
};
specScript.textContent = JSON.stringify(specRules);
document.body.append(specScript);
}, 800);
} else {
console.warn('Speculation Rules API is not supported in this browser');
}
// Helper function to create speculation rules script
function createSpeculationRules(url) {
if (!HTMLScriptElement.supports?.('speculationrules')) {
console.warn('Speculation Rules API is not supported in this browser');
return null;
}
const specScript = document.createElement('script');
specScript.type = 'speculationrules';
const specRules = {
prerender: [{
source: "list",
urls: [url],
eagerness: "immediate"
}]
};
specScript.textContent = JSON.stringify(specRules);
return specScript;
}
// Function to watch for element availability in DOM
function watchForElement(selector, callback) {
// First try to get the element directly
const element = document.querySelector(selector);
if (element) {
callback(element);
return;
}
// If it doesn't exist, set up the observer
const observer = new MutationObserver((mutations, obs) => {
const element = document.querySelector(selector);
if (element) {
callback(element);
observer.disconnect(); // Stop observing once found
}
});
// Start observing
observer.observe(document.documentElement, {
childList: true,
subtree: true
});
// For safety, disconnect after a reasonable timeout
setTimeout(() => observer.disconnect(), 2000);
}
// Usage
watchForElement('section:nth-child(3) > ul > li:nth-child(1) a', (element) => {
const specScript = createSpeculationRules(element.href);
if (specScript) {
document.body.append(specScript);
}
});
<script type="speculationrules">
{
"prerender": [
{
"where": { "href_matches": "/*" },
"eagerness": "moderate"
}
],
"prefetch": [
{
"where": {
"and": [
{ "href_matches": "/*" },
{ "not": {"href_matches": "/wp-admin"}},
{ "not": {"href_matches": "/*\\?*(^|&)add-to-cart=*"}},
{ "not": {"selector_matches": ".do-not-prerender"}},
{ "not": {"selector_matches": "[rel~=nofollow]"}}
]
},
"eagerness": "moderate"
}
]
}
</script>
if (HTMLScriptElement.supports &&
HTMLScriptElement.supports('speculationrules')) {
const specScript = document.createElement('script');
specScript.type = 'speculationrules';
specRules = {
prerender: [
{
urls: ['/next.html'],
},
],
};
specScript.textContent = JSON.stringify(specRules);
console.log('added speculation rules to: next.html');
document.body.append(specScript);
}
🫠
Speculation-Rules: "/speculationrules.json"
Content-Type: application/speculationrules+json
Access-Control-Allow-Origin: *
Speculation rules can also be delivered by using a Speculation-Rules HTTP header, rather than including them directly in the document's HTML. This allows easier deployment by CDNs without the need to alter document contents themselves.
Caution: Over-speculation has a clear cost to users for bandwidth, memory, and CPU costs, but also for sites themselves. Be conscious of the impact you are putting on your users when implementing speculations. The eagerness settings allow you to reduce the likelihood of wasted speculations. Chrome also places limits on speculations to reduce the impact of overuse.
Impact on UX
😬
Impact on analytics
// Set up a promise for when the page is activated,
// which is needed for prerendered pages.
const whenActivated = new Promise((resolve) => {
if (document.prerendering) {
document.addEventListener('prerenderingchange', resolve, {once: true});
} else {
resolve();
}
});
async function initAnalytics() {
await whenActivated;
// Initialise your analytics
}
initAnalytics();
😬
Impact on analytics
// Set up a promise for when the page is first made visible
const whenFirstVisible = new Promise((resolve) => {
if (document.hidden) {
document.addEventListener('visibilitychange', resolve, {once: true});
} else {
resolve();
}
});
async function initAnalytics() {
await whenFirstVisible;
// Initialise your analytics
}
initAnalytics();
😬
// astro.config.mjs
{
prefetch: {
prefetchAll: true,
defaultStrategy: 'viewport',
},
experimental: {
clientPrerender: true,
},
}
Barry Pollard
📚
📚
📚
📚
💻
💻
📓
By Joan León
Discover how the Speculation Rules API can help us improve the UX of our website's navigation flows. We'll learn how the API works, and how to use it to "pre-render" pages (in Chromium browsers) to achieve instant navigation.