← All posts

Speculation Rules: Instant Navigations With Prefetch and Prerender

VisibilityIQ

You’ve spent months pushing LCP from 3.2 seconds to 2.1. Real gains, real work. Meanwhile a competitor’s site feels instant — click a link and the next page is just there, no spinner, no flash. They didn’t make their pages load faster. They made the load happen before the click, so by the time the user navigates, there’s nothing left to load. That’s a different category of optimization, and the Speculation Rules API is how it’s done.

The premise inverts the usual performance model. Conventional CWV work shortens the time between navigation and paint. Speculation Rules eliminate that time for predicted navigations by doing the work speculatively — fetching, and optionally fully rendering, the next likely page while the user is still reading the current one. When the prediction is right, navigation becomes an instant swap of an already-rendered page. The metric you’re optimizing isn’t how fast a page loads; it’s whether the user ever perceives a load at all.

The API: Declarative Speculation

Speculation Rules is a declarative JSON API embedded in the page. You tell the browser which URLs are candidates for prefetch or prerender and how eager to be; the browser handles the heavy lifting — connection setup, fetching, rendering, memory budgeting, and abandoning speculations that turn out wrong.

<script type="speculationrules">
{
  "prerender": [{
    "where": { "href_matches": "/products/*" },
    "eagerness": "moderate"
  }],
  "prefetch": [{
    "where": { "href_matches": "/*" },
    "eagerness": "conservative"
  }]
}
</script>

where selects the URLs — by pattern (href_matches), by CSS selector matching specific links, or by explicit URL list. eagerness controls the trigger and ranges across immediate (start now), eager (on the slightest signal), moderate (on hover, roughly 200ms of pointer dwell), and conservative (on pointerdown, the moment just before the click commits). The eagerness dial is the entire risk-versus-reward control: immediate prerender of everything would render dozens of pages the user never visits, wasting their bandwidth and battery; conservative prefetch fires only when a click is nearly certain, wasting almost nothing but saving less.

This is a deliberate replacement for the old <link rel="prefetch"> and the deprecated <link rel="prerender">, which were cruder, gave the browser no eagerness signal, and — in prerender’s case — were removed precisely because they ran unconditionally and wastefully. Speculation Rules give the browser enough information to speculate intelligently and to cancel cleanly.

Prefetch Versus Prerender: Two Levels of Pre-Work

The two modes sit at very different points on the cost-benefit curve, and choosing wrongly either wastes resources or under-delivers.

Prefetch retrieves the next page’s HTML document and parks it in cache. On navigation, the browser skips the network fetch of the document — but still has to parse the HTML, build the DOM and CSSOM, fetch subresources, execute JavaScript, and paint. You’ve removed one round trip, typically saving the TTFB-and-document-download portion: a few hundred milliseconds on a slow connection, less on a fast one. It’s cheap, it’s low-risk (you’ve only downloaded one HTML file the user might not use), and it’s the right default for broad, lower-confidence predictions across many links.

Prerender retrieves the document and renders the entire page in a hidden state — running scripts, fetching CSS and images, laying out, painting — so it sits fully constructed and interactive, invisible, waiting. On navigation the browser activates it: the hidden page becomes the visible page in a single swap, often in well under 100ms, because every expensive step already happened. The user experiences not a fast load but the absence of a load.

The cost asymmetry is the whole story. Prefetch spends one document download per speculation. Prerender spends a full page render — CPU, memory, every subresource — per speculation, all of it speculative and wasted if the prediction misses. That’s why you gate prerender behind higher-confidence eagerness (moderate/hover or conservative/pointerdown) and reserve it for high-value, high-probability next steps: the product page from a category listing, the article from a homepage card, step two of a checkout. Prefetch the broad set; prerender the sure thing.

Eligibility and the Constraints That Bite

Speculation isn’t free to apply, and several real constraints govern what’s eligible. Knowing them prevents the frustrating case where you’ve added the rules and nothing speculates.

Prerendered pages must tolerate running in a hidden, pre-activation state. Same-origin prerendering is the well-supported baseline; cross-origin prerender is far more restricted because rendering another origin’s page invisibly raises obvious privacy and resource concerns. Pages that depend on being the foreground tab at load — that read visibility, that assume they’re focused, that fire effects immediately — need to be prerender-aware or they misbehave.

Server state is the sharpest edge. A prerender issues a real GET request to your server ahead of the actual visit, so any endpoint with side effects on GET — incrementing a view counter, mutating a cart, logging a “real” pageview — fires speculatively, sometimes for a navigation that never happens. GET requests must be safe and idempotent (which they should have been anyway), and analytics or impression logic must defer to activation. The platform exposes document.prerendering (true while in the hidden render) and the prerenderingchange event (fires on activation); you guard side-effecting code behind them:

if (document.prerendering) {
  document.addEventListener('prerenderingchange', sendPageview, { once: true });
} else {
  sendPageview();
}

Mature analytics libraries already do this internally, but any hand-rolled impression, A/B-assignment, or ad-counting code you own needs the same guard or it corrupts its own data with phantom prerender events.

Headers and meta directives still apply: a Cache-Control: no-store, certain CSP configurations, and a few other response characteristics make a document ineligible for speculation, and the browser silently declines. When a rule doesn’t fire, the Application panel’s Speculative Loads section in DevTools reports the status and the reason — “not eligible,” “failure,” “ready” — which is the first place to look when speculation seems inert.

The resource budget is the quiet governor over everything. Prerendering is, by design, a bet placed with the user’s hardware: every speculative render consumes real memory and CPU on their device, spent on a page they may never see. The browser knows this and enforces its own limits — it caps the number of concurrent prerenders, deprioritizes or abandons speculations under memory pressure, and will simply refuse to prerender on low-end devices or when the tab is backgrounded. This is why eagerness: immediate across a broad href_matches pattern is a mistake rather than a shortcut: you’re not commanding the browser to prerender everything, you’re asking it to, and it will quietly decline most of the requests while wasting cycles evaluating them. The discipline is to make the high-confidence predictions explicit and narrow — the two or three links a user on this page is most likely to click next — and let prefetch handle the speculative long tail at near-zero cost. A tight prerender rule that the browser actually honors beats an ambitious one it ignores.

The Bfcache Parallel and the INP/LCP Payoff

There’s a clean conceptual link to the back/forward cache. Bfcache makes backward navigation instant by snapshotting the page you’re leaving and restoring it whole on Back; prerender makes forward navigation instant by constructing the page you’re heading to before you arrive. Both deliver the same user experience — a navigation with no perceptible load — by ensuring the destination is already rendered. The same hygiene that keeps a page bfcache-eligible (no unload handlers, no no-store, no long-lived blocking connections) tends to keep it prerender-friendly, so the two optimizations reward the same disciplined page construction.

The Core Web Vitals payoff is concrete but specifically scoped. For a successfully prerendered navigation, LCP is measured from activation, and the largest element is usually already painted at activation time — so LCP lands in the tens of milliseconds. INP benefits too: the page’s JavaScript has already initialized and hydrated during the hidden render, so the main thread is quiet and responsive the instant the user can interact, rather than busy with startup work during those critical first interactions. The navigation that would have measured a 2.4s LCP and a janky first interaction instead measures near-zero on both.

The honest boundary: these wins apply only to predicted, prerendered, second-and-subsequent navigations. The first page a user lands on — the entry point from search, from an ad, from a shared link — is never prerendered, because there was no prior page to host the speculation rules. Its CWV is whatever your conventional optimization made it. Speculation Rules optimize the journey through the site, not the entry to it. They’re a complement to LCP and INP work on landing pages, not a replacement — you still need fast entry pages, and then you make every step after the entry instant.

The Practical Sequence

Identify the dominant navigation paths first — the category-to-product click, the homepage-to-article click, the checkout funnel. Those high-probability transitions are where prerender’s expensive speculation pays off. Add a Speculation Rules script that prerenders that narrow, high-confidence set at moderate or conservative eagerness, and prefetches the broader link population at conservative for cheap, low-risk coverage.

Then audit for the side-effect traps: confirm GET endpoints are idempotent, guard analytics and impression code behind document.prerendering and prerenderingchange, and verify in the DevTools Speculative Loads panel that your rules actually reach “ready” rather than silently failing eligibility. Measure the win on the second navigation — click into the site, then click the predicted link, and confirm the near-instant activation — because the first-load metrics won’t show it by design.

VisibilityIQ’s performance audit detects whether a site ships Speculation Rules at all, flags prerender directives that will fire side effects on non-idempotent GET endpoints or run analytics without an activation guard, and checks the same page-construction hygiene (no-store, unload handlers) that governs both prerender and bfcache eligibility — surfacing the instant-navigation opportunity alongside the conventional LCP and INP findings rather than treating next-navigation speed as a separate concern.

Frequently asked questions

What's the difference between prefetch and prerender in Speculation Rules?
Prefetch fetches the next page's HTML document in the background and holds it in cache, so when the user navigates, the document is already downloaded — saving the network round trip but still requiring the browser to parse, render, and run scripts on navigation. Prerender goes much further: it fetches the document and fully renders the page in a hidden tab, executing JavaScript and loading subresources, so on navigation the browser swaps in an already-painted, already-interactive page in milliseconds. Prefetch is cheaper and lower-risk; prerender delivers a near-instant navigation but consumes more memory and CPU speculatively, so you reserve it for high-confidence predictions.
Does prerendering hurt my analytics or run code prematurely?
It can if you don't account for it. A prerendered page executes its scripts in the hidden state, which means analytics, ad impressions, and side-effecting code could fire before — or without — the user ever visiting. The platform provides the document.prerendering property and the prerenderingchange event to gate this: code checks whether it's running in a prerender and defers impression counting, A/B assignment, or any user-attributable side effect until activation. Well-behaved analytics libraries already do this. The fix is to wrap side-effecting startup code so it waits for activation rather than running on load.
How does prerendering interact with Core Web Vitals?
It transforms them for the prerendered navigation. Because the page is already rendered when the user activates it, LCP is effectively measured from activation, and the largest element is typically already painted — producing LCP values in the tens of milliseconds. The activation also makes the page eligible to be served from a state similar to bfcache, so the load feels instant. The catch: these wins apply only to the second and subsequent navigations that were successfully predicted and prerendered. The first page a user lands on is never prerendered, so its CWV is unaffected. Speculation Rules optimize the journey, not the entry.