Speculation Rules in Practice: Prerendering Your Way to Instant Navigations

Most performance work on the web is about making pages load faster once the user asks for them. The Speculation Rules API flips that around: it lets you tell the browser which pages a user is about to visit, so the navigation itself becomes instant. Instead of shaving milliseconds off a render, you eliminate it from the user’s experience entirely.

The API has quietly matured into one of the highest-leverage performance features available in Chrome-based browsers. Prerendering support landed in Chrome 109, document rules and the eagerness system arrived in Chrome 121, and the rules themselves can now even be delivered from your CDN via an HTTP header instead of touching page HTML. If you dismissed it early as another experimental hint, it’s worth a second look.

How the Two Levels Work: Prefetch vs Prerender

Speculation rules come in two strengths. prefetch fetches the target document ahead of time, so when the user clicks, the response is already in the HTTP cache. prerender goes much further: Chrome renders the full page in a hidden state, executes its JavaScript, and holds it in memory. On activation — the actual click — the page swaps in with no network wait and no rendering work. For a prerendered navigation, users perceive load time as effectively zero.

The simplest form is a static list of URLs, best for the one or two pages you know most users visit next:

<script type="speculationrules">
{
  "prerender": [{
    "urls": ["/next-article", "/pricing"]
  }]
}
</script>

List rules fire immediately by default, which is exactly what you want for a known next step. The more interesting case is reacting to user intent, and that’s where document rules and eagerness come in.

Document Rules: Declarative Intent Detection

Document rules apply speculation to every link in the page that matches a condition, using the where syntax with href_matches patterns and CSS selectors. This replaces the hover-listener JavaScript libraries people have shipped for years:

<script type="speculationrules">
{
  "prerender": [{
    "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>

That single block prerenders any same-site link the user shows intent toward, while excluding admin pages, cart-mutating URLs, and anything you’ve explicitly marked. The URL patterns follow the URL Pattern API, and the selector_matches conditions compose with and/not logic, so the exclusion rules can be as precise as your application needs.

Eagerness: How Much Intent Is Enough

The eagerness field controls when a speculation fires, and picking the right level is the whole cost/benefit tradeoff in one setting:

  • conservative — fires on pointer or touch down. Almost a guaranteed hit, but saves the least time.
  • moderate — on desktop, fires after the pointer hovers a link for 200 milliseconds, or on pointerdown if that comes first. On mobile, it uses viewport heuristics that trigger 500 ms after scrolling stops, for links near where the user tapped.
  • eager — on desktop, a 10 millisecond hover. On mobile since January 2026, simple viewport heuristics fire 50 ms after an anchor scrolls into view. This used to behave like immediate but changed in Chrome 143.
  • immediate — speculate as soon as the rules are observed. Meant for lists of known-next URLs, not whole documents.

Chrome enforces limits that prevent abuse, and they’re low enough to matter. Interaction-based rules (eager, moderate, conservative) are capped at 2 concurrent speculations each for prefetch and prerender, evicted FIFO — so hovering across a navigation menu speculates the last two links you touched, not the whole menu. Static list rules get 50 prefetches and 10 prerenders. Chrome also refuses to speculate when Save-Data is on, in energy-saver mode on low battery, under memory pressure, or when the user has disabled page preloading, and it doesn’t render cross-origin iframes on prerendered pages until activation.

Detecting and Surviving Prerendering

Prerendering executes your JavaScript before anyone has chosen to view the page. Anything with side effects — analytics beacons, polling loops, recording sessions — should wait for activation. The platform gives you the hooks to do this cleanly:

// Resolves when the page is actually viewed,
// immediately if it was never prerendered.
const whenActivated = new Promise((resolve) => {
  if (document.prerendering) {
    document.addEventListener('prerenderingchange', resolve, { once: true });
  } else {
    resolve();
  }
});

async function initAnalytics() {
  await whenActivated;
  // Start analytics, polling, anything with side effects
}

initAnalytics();

After activation, you can confirm the navigation was prerendered by checking performance.getEntriesByType('navigation')[0].activationStart in the console — a non-zero value means the page was served from a prerender. That’s also the signal to segment your performance data on: prerendered navigations will show dramatically better Core Web Vitals, and mixing them into your averages will quietly flatter every metric you track.

Rules From the Edge, and What’s Next

For sites where editing HTML is awkward, Chrome 121 added the Speculation-Rules response header. Point it at a JSON file served with the right MIME type, and your CDN can inject rules without touching the document:

Speculation-Rules: "/speculationrules.json"

On the roadmap is prerender_until_script, a middle ground between prefetch and full prerender: it fetches the document and its subresources and starts rendering, but stops at the first script tag. Pages with no JavaScript, or script only in the footer, could be almost fully prerendered without risking the side effects of head-deployed analytics and A/B tooling executing before anyone asked for the page.

Support outside the Chromium family is still the caveat: Firefox has not shipped support, and Safari remains behind a flag. But the API is a progressive enhancement in the truest sense — a single script tag that Chromium browsers act on and everyone else safely ignores. The official documentation covers the full field list, CSP interactions, and debugging in DevTools. Start with a moderate document rule, segment your metrics on activationStart, and let the hit-rate data tell you how much more of the funnel deserves to be instant.

Leave a Reply

Your email address will not be published. Required fields are marked *