The Page You Never Opened Has Already Loaded
SnapFrom Team · · 7 min read
Two hundred milliseconds. That is roughly how long your pointer has to rest on a link before Chrome may decide to load the page behind it[3].
Loading here means what it sounds like: every subresource fetched, every line of the page’s JavaScript executed, and the network requests that JavaScript then makes[1].
You have not clicked. You may never click. The page has loaded anyway.
A click stopped being the thing that starts a page load
The mechanism is the Speculation Rules API, and it has two settings that sound similar and are not.
A prefetch rule makes the browser “download the response body of the referenced pages, but none of the subresources referenced by the page”[1]. That is a head start on a page load. The HTML arrives early and nothing acts on it.
A prerender rule makes the browser “fetch, render, and load the content into an invisible tab, stored in a per-document in-memory cache”[1]. MDN then spells out what that includes, and this is the sentence the whole subject turns on: “loading all subresources, running all JavaScript, and even loading subresources and performing data fetches started by JavaScript”[1].
So a prerender is not preparation for a page load. It is a page load. The only thing that has not happened is you seeing it.
The site chooses how little of you it needs
Which gesture is enough is not the browser’s decision. The site declares it, in a field called eagerness, with four values.
conservative starts the speculation “only when the user is starting to click on the link, for example on mousedown or pointerdown”[2]. That one is hard to argue with. You have committed; the browser is just fast.
moderate starts “when there is a reasonable suggestion that the user will follow a link in the near future”[2].
eager starts “on any slight suggestion that a link may be followed”, and MDN’s own examples of a slight suggestion are that “the user could move their mouse cursor towards the link, hover/focus it for a moment, or pause scrolling with the link in a prominent place”[2].
immediate does not wait for you at all. It starts “as soon as possible, subject only to considerations such as user preferences and resource limits”[2].
The defaults are asymmetric in a way that looks backwards until you think about it: a list of explicit URLs defaults to immediate, while a rule that matches links by pattern across the document defaults to conservative[2]. Hand-pick three URLs and they load at once, unprompted. Say “every link on this page” and the browser waits for a click gesture. The safety valve is on the broad rule, not the narrow one.
In Chrome, those words have numbers behind them
The spec leaves “a reasonable suggestion” undefined. Chrome’s documentation puts numbers on it.
On desktop, moderate speculates if you hold the pointer over a link for 200 milliseconds, or on pointerdown if that comes sooner[3]. eager speculates after 10 milliseconds of hover[3]. conservative waits for pointer or touch down[3], and immediate fires as soon as the rules are read[3].
Ten milliseconds is not a hover. It is a mouse crossing a link on its way somewhere else.
There are ceilings, and they tell you what the browser thinks this costs. At immediate, Chrome allows 50 prefetches and 10 prerenders. At every other eagerness, two of each, first-in-first-out, and “after reaching the limit, a new speculation will cause the oldest speculation to be canceled and replaced by the newer one to conserve memory”[3].
A cap of two, with cancellation, is not the shape of a cheap operation.
Chrome also does this on its own account, with no rules from any site: it prerenders from what you type in the address bar and from the bookmarks bar, driven by its own predictions about where you are going[3].
The side effects are the part nobody enforces
A page that has run all of its JavaScript has done whatever that JavaScript does. MDN names the failure case without softening it: prerendering is unsafe when “the page runs JavaScript or loads images that cause side effects such as sending analytics, recording ad impressions, or otherwise modifying the state of the application as if the user had already interacted with it”[1].
There is a documented fix, and it is entirely voluntary. A page can test Document.prerendering to find out it is running invisibly, and postpone the work by listening for prerenderingchange, which fires “when the prerendered page is activated by the user viewing the page”[1].
The division of labour is the problem. The browser provides the signal; every site has to know about it, remember it, and implement it in every script it loads, including the ones it did not write.
Nothing checks. Nothing warns. A page that skips it still works perfectly, and the only visible consequence lands in someone’s analytics as a visit that never happened. There is no way to tell from the outside which sites handled it and which did not.
Whether the server can tell
It can, if it looks.
A speculative request carries a Sec-Purpose header, which “indicates the purpose for which the requested resource will be used, when that purpose is something other than immediate use by the user-agent”[4]. Its defined value describes the situation precisely: the resource is wanted “in anticipation that it will be needed by a page that is likely to be navigated to in the near future, such as a page linked in search results or a link that a user has hovered over”[4].
MDN then lists what a server may do with that knowledge: adjust the caching expiry, disallow the request outright, or treat it differently when counting page visits[4]. The header exists so that speculative traffic does not have to be mistaken for real traffic. Using it is optional.
The infrastructure side is more candid about the cost than any of the developer-facing framing. Chrome documents a Sec-Speculation-Tags header whose purpose is filtering speculations server-side, and the motivating case is stated plainly: “Some CDNs automatically inject speculation rules but block speculations for non-edge cached pages to avoid this feature resulting in increased origin-server usage”[3].
CDNs turn this on by default and then guard their own origins against it. That is a measurement of how much traffic it generates.
This is not a third-party tracking mechanism, and the boundary is exact. Prerendering “is restricted to same-origin documents by default”; cross-origin same-site prerendering requires the destination to opt in with a Supports-Loading-Mode: credentialed-prerender header, and cross-site prerendering “is not possible at this time”[1]. What loads without your decision is the site’s own pages, with your own cookies attached.
What this costs you
Bytes you did not ask for. MDN’s calibration is that “a prerender uses about the same amount of resources as rendering an <iframe>”, and its warning is direct: “Prerendering uses memory and network bandwidth. If you prerender something the user doesn’t navigate to, these are wasted”[1]. On a metered connection, the waste is yours.
Being counted as a reader of pages you never read. The correction requires every site and every embedded script to opt into handling it[1], and the header that would let a server separate speculative traffic from real traffic is one it has to choose to inspect[4].
A gesture you did not know was one. At eager, ten milliseconds of pointer travel is consent[3]. At immediate, nothing at all is required of you[2].
You do have a lever, and it is more effective than most privacy controls. Chrome suppresses speculation when the “Preload pages” setting is off, when Save-Data is on, under energy saver at low battery, under memory pressure, for pages in background tabs, and when an extension such as uBlock Origin disables it[3]. Turning off “Preload pages” costs you some latency and takes back the bandwidth.
None of this is a scandal. Speculative loading is a real speed win, it is mostly confined to a site’s own pages, and the specifications are unusually frank about the trade being made. What it changes is narrower than a privacy breach and stranger: the page load is no longer something you start. Chrome will start it for you at two hundred milliseconds of hover, and the site chose that number, not you.
Sources
- Speculation Rules API
- MDN Web Docs, Mozilla, accessed
Supports: A prefetch rule causes browsers to 'download the response body of the referenced pages, but none of the subresources referenced by the page'. A prerender rule causes browsers to 'fetch, render, and load the content into an invisible tab, stored in a per-document in-memory cache', and this 'includes loading all subresources, running all JavaScript, and even loading subresources and performing data fetches started by JavaScript'. Unsafe prerendering is described as the case where 'the page runs JavaScript or loads images that cause side effects such as sending analytics, recording ad impressions, or otherwise modifying the state of the application as if the user had already interacted with it', and the documented mitigation is to check Document.prerendering and defer the work to the prerenderingchange event, which fires 'when the prerendered page is activated by the user viewing the page'. On cost: 'a prerender uses about the same amount of resources as rendering an <iframe>', and 'Prerendering uses memory and network bandwidth. If you prerender something the user doesn't navigate to, these are wasted.' On scope: 'Prerendering is restricted to same-origin documents by default', cross-origin same-site prerendering 'requires the navigation target to opt-in using the Supports-Loading-Mode header with a value of credentialed-prerender', and 'Cross-site prerendering is not possible at this time.'
- <script type="speculationrules">
- MDN Web Docs, Mozilla, accessed
Supports: The four eagerness values and their definitions: 'immediate' means the speculation 'should start as soon as possible, subject only to considerations such as user preferences and resource limits'; 'eager' means it 'should start on any slight suggestion that a link may be followed. For example, the user could move their mouse cursor towards the link, hover/focus it for a moment, or pause scrolling with the link in a prominent place'; 'moderate' means it 'should start when there is a reasonable suggestion that the user will follow a link in the near future'; 'conservative' means it 'should start only when the user is starting to click on the link, for example on mousedown or pointerdown'. And the defaults: 'If "eagerness" is not explicitly specified, list ("urls") rules default to immediate and document ("where") rules default to conservative.'
- Prerender pages in Chrome for instant page navigations
- Chrome for Developers, Google, accessed
Supports: Chrome's implemented thresholds per eagerness on desktop: conservative 'speculates on pointer or touch down'; moderate 'performs speculations if you hold the pointer over a link for 200 milliseconds', or on the pointerdown event if that comes sooner; eager speculates after holding the pointer over a link for 10 milliseconds; immediate speculates 'as soon as the speculation rules are observed'. Concurrency limits: 50 prefetches and 10 prerenders at immediate eagerness, and 2 each under eager, moderate and conservative, applied first-in-first-out, where 'after reaching the limit, a new speculation will cause the oldest speculation to be canceled and replaced by the newer one to conserve memory'. Chrome also triggers prerendering itself from address bar typing and the bookmarks bar, driven by its own predictions. The tag field on a ruleset 'is then reflected in the Sec-Speculation-Tags HTTP header, which can be used to filter speculation rules at the server', and the motivating case is that 'Some CDNs automatically inject speculation rules but block speculations for non-edge cached pages to avoid this feature resulting in increased origin-server usage.' Chrome suppresses speculation when the 'Preload pages' setting is off, when Save-Data is enabled, under energy saver on low battery, under memory constraints, for pages in background tabs, and when an extension such as uBlock Origin disables it.
- Sec-Purpose header
- MDN Web Docs, Mozilla, accessed
Supports: Sec-Purpose is a fetch metadata request header that 'indicates the purpose for which the requested resource will be used, when that purpose is something other than immediate use by the user-agent'. Its defined value, prefetch, 'indicates that the resource is being requested in anticipation that it will be needed by a page that is likely to be navigated to in the near future, such as a page linked in search results or a link that a user has hovered over.' A server receiving it may adjust the caching expiry for the request, disallow the request, or treat it differently when counting page visits.