Speculation Rules and Prerendering: What Analytics Gets Wrong About Prefetched Pages
Chrome's Speculation Rules API can render a page before a user ever clicks. Here's how it distorts naive analytics — and how cookieless, edge-based measurement stays honest.
The page that loaded before the click
The Speculation Rules API, now broadly shipped in Chromium browsers, lets a site declare which URLs the browser should prefetch or fully prerender ahead of navigation. When a user hovers or shows intent, the destination can already be rendered in a hidden tab, then swapped in instantly. The payoff is a near-zero Largest Contentful Paint on activation. The problem, for measurement, is that a prerendered page runs your scripts before anyone has decided to visit it.
If your analytics fires a pageview on load, prerendering inflates your counts with visits that never happened. Worse, it distorts performance data: a page prerendered in the background records timings that bear no relation to what a real user experienced.
What actually happens during prerender
A prerendered document executes normally, but in a special lifecycle state. document.prerendering is true, and the page has not yet been activated into the visible tab. The prerenderingchange event fires when — and only if — the user actually navigates to it. Many prerenders are never activated at all; the browser may discard them.
This matters for two reasons:
- Counting. A naive
sendBeacononDOMContentLoadedwill report a pageview for a document that may never be seen. - Performance attribution. Core Web Vitals are defined relative to user-perceived loading. The web-vitals library and the underlying Paint Timing and Event Timing specs account for prerender by measuring from the activation point, not from the original navigation start. A metric collected without that awareness is simply wrong.
The correct pattern
The web platform gives you clean signals. Defer any measurement until the page is genuinely visible to a person:
if (document.prerendering) {
document.addEventListener('prerenderingchange', recordPageview, { once: true });
} else {
recordPageview();
}
For timing, PerformanceNavigationTiming.activationStart gives the offset between navigation start and activation. Any paint or interaction timestamp should be measured relative to that value, clamping negatives to zero. This is exactly what Google's web-vitals library does internally, so if you compute LCP or INP yourself, mirror that logic.
The same discipline applies to prefetch, the lighter cousin of prerender. Prefetch only fetches the response; it does not execute scripts, so it rarely pollutes analytics directly. But it does consume bandwidth and can skew server-side request logs — a reminder that raw log counts and genuine visits diverge more than ever.
Why this is easier without cookies or client state
Speculation-driven prerender is a headache for stateful trackers. A prerendered document that sets identifiers, hydrates a session, or writes to storage does so speculatively — for a visit that may be discarded. Cleaning that up, deduplicating against the eventual real activation, and reconciling cross-page identity becomes a genuine source of error and privacy risk.
Monoid sidesteps the entire class of problem. We store nothing on the device — no cookies, no localStorage, no fingerprint. There is no speculative session to create and later un-create. Measurement is a single, stateless signal that we only emit once the page is real. Because collection runs at the Cloudflare edge, aggregation happens on data that already reflects genuine, activated navigations rather than a browser's optimistic guesses.
This is data minimisation working in your favour. When you collect nothing that needs cleaning up, speculative rendering does not create orphaned identity records or duplicate profiles. It just means you wait for prerenderingchange before counting.
A checklist for prerender-safe analytics
- Gate pageview emission on
document.prerenderingand theprerenderingchangeevent. - Compute LCP, FCP, and INP relative to
activationStart, or use a library that already does. - Treat server request logs as a superset of real visits, not a proxy for them.
- Confirm your Content-Security-Policy allows your measurement endpoint to be reached from an activated document; test in Chrome DevTools' prerender pane.
- Avoid any speculative writes to device storage — the cleanest way to be prerender-correct is to hold no client state at all.
The wider point
Each new performance primitive — bfcache, soft navigations, and now speculation rules — quietly breaks the old assumption that a page load equals a human visit. Analytics that leans on cookies and stateful sessions has to bolt on ever more reconciliation logic. Analytics that measures a stateless event at the moment of genuine visibility just needs to respect the platform's lifecycle events. Faster pages and honest numbers are not in tension; you simply have to count at the right moment.
Comments
Loading comments…