Speculation Rules API
Prefetch vs. Prerender
By default the browser does nothing until you click, then kicks off the full sequence from scratch: DNS, TCP, TLS, HTML fetch, parse, JS execute, render. All of that happens after you’ve committed to navigating.
Both prefetch and prerender move that work earlier, but they stop at different points.
Prefetch stops after the network. It downloads the HTML and sub-resources into cache, but the page isn’t parsed or rendered. You still pay the rendering cost on click, just not the network cost.
Prerender goes further. It fetches, parses, executes JS, lays out, and paints in a hidden background tab. When you click, the browser swaps the already-rendered page in.
Prefetch strategies
| Strategy | Triggers on |
|---|---|
tap | mousedown, just before click |
hover | hover delay (~80ms) |
viewport | link scrolls into view |
load | every link, immediately on page load |
Prerendering
You can trigger it per-link with rel="prerender":
<link rel="prerender" href="/about" />The opposite end is lazy loading: defer everything until explicitly needed (loading="lazy", dynamic imports, IntersectionObserver).
TLDR
Prerender pays upfront. Lazy loading pays on demand.
The Speculation Rules API
Chrome 109+ lets you control this with a JSON script tag:
<script type="speculationrules">
{"prerender":[{"where":{"href_matches":"/*"},"eagerness":"moderate"}]}
</script>eagerness controls when the browser triggers prerendering:
| Value | Triggers on |
|---|---|
conservative | mousedown |
moderate | hover / focus |
eager | link in DOM |
moderate is the right default. Hover is clear enough intent, and a wrong guess just wastes a bit of bandwidth.
Prefetch + prerender compose well
Astro’s prefetch: { defaultStrategy: 'viewport' } fires when a link scrolls into view, warming the cache early. By hover time the assets are already local, so the prerender finishes fast:
link enters viewport → prefetch (network)
↓
user hovers → prerender (full render, from cache)
↓
click → instant swapDon’t prerender pages that run side effects on load — analytics hits, impression counters. The browser executes those during prerender, before the user has actually visited.
On this site view counts are incremented by a React component on mount, which only hydrates on real navigation, so it’s safe.
Other browsers silently ignore the script tag, so no broken behaviour as a fallback.