Key Takeaway
The Speculation Rules API lets you add a script type speculationrules JSON block that tells Chromium browsers which URLs to prefetch or prerender before a click. Prefetch downloads the response body; prerender fully renders the page in a hidden tab for near-instant navigation. Eagerness settings control how aggressively speculation fires.
The gap between a click and a painted page is where users feel a site as slow. I can shave milliseconds off the server, but the biggest single win is often to do the work before the click happens at all. The Speculation Rules API is the browser-native way to do exactly that: you declare which pages a visitor is likely to visit next, and Chromium fetches or even fully renders them in the background.
It replaces the older, more limited resource hints like link rel prefetch with a JSON grammar that can match links by URL pattern, choose between a cheap prefetch and a full prerender, and tune how eagerly the browser acts. In this post I walk through the syntax, the trade-off between prefetch and prerender, the eagerness levels, how it compares to the Next.js Link component, and the pitfalls I have hit in production.
What the Speculation Rules API is
You opt in by dropping a script tag with the type speculationrules into the page. Its body is a JSON object with two possible keys, prefetch and prerender, each holding an array of rules. The simplest form is a list rule: you name the exact URLs you want the browser to speculate on. Feature detection is one line — HTMLScriptElement.supports with the argument speculationrules returns true where the API exists.
<script type="speculationrules">
{
"prefetch": [
{
"source": "list",
"urls": ["/pricing", "/docs/getting-started"]
}
],
"prerender": [
{
"source": "list",
"urls": ["/checkout"]
}
]
}
</script>Prefetch versus prerender: two very different costs
A prefetch downloads only the response body of the target page and holds it in an in-memory cache. It does not fetch subresources, and it does not execute the page's JavaScript. The cost is low, and the payoff on navigation is that the HTML is already there — you skip the network round trip but still parse, render, and run scripts after the click.
A prerender goes much further: the browser loads the page into a hidden tab, fetches every subresource, and runs its JavaScript, so activation on click is close to instant. The cost is correspondingly high — roughly that of opening the page in an invisible iframe. Prerender is same-origin by default; cross-origin same-site prerendering needs an explicit opt-in header from the target.
Reach for prefetch as your safe default and promote a link to prerender only when you are confident it will be clicked — a checkout button, a paginated next page, the top search result. Prerender buys the most speed but spends the most bandwidth, memory, and CPU, so it should be earned, not sprinkled everywhere.
Document rules and href matching
Hardcoding URL lists does not scale for a content-heavy site. Document rules solve this: instead of urls, you provide a where clause that matches links already on the page. href_matches takes a URL Pattern, and selector_matches takes a CSS selector, and you combine them with and and not. The rule below prerenders every same-origin link except the logout route and anything a class explicitly marks as unsafe.
<script type="speculationrules">
{
"prerender": [
{
"where": {
"and": [
{ "href_matches": "/*" },
{ "not": { "href_matches": "/logout" } },
{ "not": { "selector_matches": ".no-prerender" } }
]
},
"eagerness": "moderate"
}
]
}
</script>Eagerness: from conservative to immediate
The eagerness field decides when the browser acts on a rule. There are four values, and the default differs by rule type — list rules default to immediate, document rules default to conservative:
- immediate — speculate as soon as the rules are parsed, subject only to browser resource limits. This is the default for list rules where you have already committed to specific URLs.
- eager — act on the slightest hint, such as the cursor moving toward a link or a brief hover. Good for prefetching many likely destinations early.
- moderate — the balance point: fire when there is a reasonable signal, roughly a 200-millisecond hover or focus. A sensible choice for document-rule prerendering.
- conservative — wait until the user is essentially committing, on pointerdown or mousedown. This is the default for document rules and the cheapest interaction-based option.
Chrome caps speculation to protect the device: at immediate or eager eagerness it allows up to 50 prefetches and 10 prerenders, but only 2 of each for the interaction-based moderate and conservative levels. Over-speculating wastes the user's bandwidth, memory, and CPU — and every prerender that is never clicked is pure waste, so match eagerness to real click likelihood.
How it differs from Next.js Link prefetch
If you build with Next.js, the Link component already prefetches — but it is a different mechanism, not the Speculation Rules API. Next.js prefetches the route's own payload, the RSC data and JavaScript needed for a client-side transition, when a Link enters the viewport in production. Speculation Rules is a browser feature that can prefetch or fully prerender a real document navigation. The two are complementary rather than interchangeable:
| Aspect | Next.js Link prefetch | Speculation Rules API |
|---|---|---|
| Who runs it | The Next.js framework in your app | The Chromium browser itself |
| What is loaded | Route RSC payload and JS for a client transition | A full document: HTML for prefetch, or a rendered page for prerender |
| Trigger | Link enters the viewport, in production only | Eagerness rules — immediate, eager, moderate, or conservative |
| Navigation type | Client-side SPA transition within the app | Real browser navigation to a URL |
| Browser support | Any browser that runs your React app | Chromium only — Chrome and Edge, not Firefox or Safari |
Pitfalls I watch for
Speculation is powerful, but a few failure modes bite in production if you are not careful:
- Wasted prerenders. A prerender that is never activated has already spent full page cost. Aggressive document rules over broad selectors can prerender dozens of pages a user never opens — prefer conservative or moderate eagerness and narrow href_matches patterns.
- Analytics double-counting. Because a prerendered page runs its JavaScript before it is visible, naive analytics fire a pageview during prerender and again on activation. Guard initialization behind document.prerendering and the prerenderingchange event so a view is counted only when the user actually sees the page.
- Unsafe side effects on load. Any URL that mutates state on GET — sign-out, add-to-cart, one-time tokens, usage counters, ad impressions — must be excluded with a not href_matches clause, or a prerender will trigger it before the user ever clicks.
The analytics guard is small. Google Analytics and the Google Publisher Tag already account for prerendering, but for anything custom you defer initialization until the page is genuinely shown:
if (document.prerendering) {
document.addEventListener(
"prerenderingchange",
initAnalytics,
{ once: true }
);
} else {
initAnalytics();
}Browser support is the honest catch: the Speculation Rules API is Chromium-only. Chrome shipped prerendering via speculation rules in version 109 and the eagerness field in version 121, and Edge mirrors those releases. Firefox and Safari do not support it. That is fine — it is a progressive enhancement, so unsupported browsers simply navigate normally while Chromium users get the instant experience. Start with a conservative document rule on one high-intent link, watch your activation rate, and widen from there.

