Speculation

 navigation with

 Instant

 Rules API

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

Chrome UX Report 📊
The 202410 release is now live on BigQuery!

Lab

Field

Loading

LCP

Largest Contentful Paint 

How does it work?

chrome://predictors/

A Use Case

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

  • immediate: This is used to speculate as soon as possible, that is, as soon as the speculation rules are observed.
  • eager: This behaves identically to the immediate setting, but in future, we are looking to place this somewhere between immediate and moderate.
  • moderate: This performs speculations if you hold the pointer over a link for 200 milliseconds (or on the pointerdown event if that is sooner, and on mobile where there is no hover event).
  • conservative: This speculates on pointer or touch down.

speculationrules/eagerness

Demo or
it didn't happen

<script type="speculationrules">
{
  "prerender": [
    {
      "source": "document",
      "where": {
        "selector_matches": 
        	"section:nth-child(3) > ul > li:nth-child(1) a"
      },
      "eagerness": "immediate"
    }
  ]
}
</script>

Use Case

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');
}

Use Case

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');
}

Use Case

// 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);
    }
});

Use Case

<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>

speculationrules/like-a-boss

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);
}

speculationrules/like-a-boss

speculationrules/like-a-boss

Single Page Application

speculationrules/support

🫠

Speculation-Rules HTTP header

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.

Chrome limits

  • When the "Preload pages" setting is turned off (which is also explicitly turned off by Chrome extensions such as uBlock Origin)
  • Pages opened in background tabs

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.

speculationrules/caution

Impact on UX

😬

speculationrules/caution

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();

😬

speculationrules/caution

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();

😬

Speed Brain: helping web pages load 45% faster

Speculative Loading
By WordPress Performance Team

// astro.config.mjs
{
  prefetch: {
    prefetchAll: true,
    defaultStrategy: 'viewport',
  },
  experimental: {
    clientPrerender: true,
  },
}

Speculation rules generator

Barry Pollard

speculationrules/resources

📚

📚

📚

📚

💻

💻

📓

Instant navigation with Speculation Rules API

By Joan León

Instant navigation with Speculation Rules API

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.

  • 203