
Predicting the Click
Achieve near-zero latency by moving beyond basic prefetching to the surgical precision of the Speculation Rules API.
Predicting the Click
Users don't care how many hours you spent optimizing your Webpack chunks if they still have to stare at a blank white screen for two seconds after clicking a link. We’ve reached the point of diminishing returns with traditional optimization; to get to "instant," we have to stop reacting to user input and start predicting it.
For years, we’ve leaned on <link rel="prefetch"> to grab assets ahead of time. It was fine, I guess—it grabbed the HTML and stuck it in the cache—but the browser still had to parse the CSS, execute the JavaScript, and paint the pixels *after* the user clicked. It was better, but it wasn't magic.
The Speculation Rules API is the magic we were looking for.
The "Hidden Tab" Strategy
Think of the Speculation Rules API as a way to open a hidden, background tab for a page the user hasn't visited yet. When the user finally clicks the link, the browser swaps that hidden tab into the foreground instantly.
The technical term is prerendering. Unlike prefetching (which just downloads the file), prerendering executes the whole page. It’s sitting there, fully rendered in memory, just waiting for its moment to shine.
Here is the most basic implementation:
<script type="speculationrules">
{
"prerender": [{
"source": "list",
"urls": ["/pricing", "/about"]
}]
}
</script>Stick that in your <head>, and the browser will start doing the heavy lifting for those two pages immediately.
Moving Past Manual Lists
Hardcoding URLs is brittle. I don't want to update a JSON object every time I add a new blog post. Fortunately, the API supports Document Rules, which lets you define patterns for what should be speculated.
I've found this is where the real power lies. You can tell the browser: "If the user hovers over a link that starts with /products/, start prerendering it."
<script type="speculationrules">
{
"prerender": [{
"source": "document",
"where": {
"and": [
{ "href_matches": "/products/*" },
{ "not": { "selector_matches": ".no-prerender" } }
]
},
"eagerness": "moderate"
}]
}
</script>Let’s talk about "Eagerness"
The eagerness setting is your throttle. It's how you balance performance with the user's data plan and your server's CPU:
* immediate: As soon as the rule is seen. Use this sparingly (maybe for your "Checkout" button).
* moderate: Prerenders if the user hovers for 200ms or moves the mouse towards the link.
* conservative: Prerenders only on pointer down (the split second between pressing the mouse and releasing it).
I usually stick with moderate. It's the sweet spot where the page feels instant but you aren't DDOSing your own server every time a user scrolls past a list of links.
The "Gotchas" (There are several)
You can't just prerender everything and call it a day. Since prerendering actually executes the page, it can trigger side effects.
1. Analytics Bloat: If you aren't careful, your analytics will show thousands of "page views" for people who just hovered over a link. You need to check document.prerendering in your scripts to delay tracking pixels until the page is actually visible.
2. State Management: If your page relies on specific session state that changes often, a prerendered page might be "stale" by the time the user sees it.
3. No Modals/Audio: You can't play sound or pop up a "Subscribe to my newsletter!" modal while a page is prerendering. The browser is smart enough to block these, but it's something to keep in mind.
Why this is better than "Instant.page"
You might have used libraries like instant.page or Quicklink in the past. They work by injecting <link rel="prefetch"> tags on hover. They’re great, but they live in the application layer.
The Speculation Rules API lives in the browser engine. It’s more efficient, it handles memory management better (it will kill a prerendered page if the system is low on RAM), and it performs a full render instead of just a fetch.
How to see if it's working
Open Chrome DevTools, go to the Application tab, and look for Speculative loads on the left sidebar. It will show you exactly what's being speculated, whether it succeeded, and—crucially—why it failed if it didn't work.
It feels like cheating, honestly. We’re so used to "optimization" being a battle of kilobytes, but here we’re just moving the clock. It’s not that the page loaded faster; it’s that it finished loading before the user even asked for it.
That's the kind of performance that actually gets noticed.
