The Speculation Rules API is designed to improve performance for future navigations. It targets document URLs rather than specific resource files, and so makes sense for multi-page applications (MPAs) rather than single-page applications (SPAs).
The Speculation Rules API provides an alternative to the widely-available <link rel="prefetch">
feature and is designed to supersede the Chrome-only deprecated <link rel="prerender">
feature. It provides many improvements over these technologies, along with a more expressive, configurable syntax for specifying which documents should be prefetched or prerendered.
Note: The Speculation Rules API doesn't handle subresource prefetches; for that you'll need to use <link rel="prefetch">
.
Concepts and usage
Speculation rules are specified inside <script type="speculationrules"> ... </script>
. The rules are specified as a JSON structure.
For example:
<script type="speculationrules">
{
"prerender": [
{
"source": "list",
"urls": ["extra.html", "extra2.html"]
}
],
"prefetch": [
{
"source": "list",
"urls": ["next.html", "next2.html"],
"requires": ["anonymous-client-ip-when-cross-origin"],
"referrer_policy": "no-referrer"
}
]
}
</script>
You specify a different array to contain the rules for each speculative loading type (for example "prerender"
or "prefetch"
). Each rule is contained in an object that specifies for example a list of resources to be fetched, plus options such as an explicit Referrer-Policy
setting for each rule. Note that prerendered URLs are also prefetched.
See <script type="speculationrules">
for a full explanation of the available syntax.
Note: As speculation rules use a <script>
element, they need to be explicitly allowed in the Content-Security-Policy
script-src
directive if the site includes it. This is done by adding the 'inline-speculation-rules'
source along with a hash- or nonce-source.
Using prefetching
Including prefetch
rules inside <script type=speculationrules> ... </script>
will cause supporting browsers to download the response body of the referenced pages, but none of the subresources referenced by the page. When a prefetched page is navigated to, it will render much more quickly than if it were not prefetched.
The results are kept in a per-document in-memory cache. Any cached prefetches are discarded when you navigate away from the current page, except of course a prefetched document that you then navigate to.
This means that if you prefetch something the user doesn't navigate to, it is generally a waste of resources, although the result may populate the HTTP cache if headers allow. That said, the upfront cost of a prefetch is much smaller than the upfront cost of a prerender, so you are encouraged to adopt prefetching broadly, for example prefetching all of the significant pages on your site, provided they are safe to prefetch (see Unsafe speculative loading conditions for more details).
Same-site and cross-site prefetches will work, but cross-site prefetches are limited (see "same-site" and "cross-site" for an explanation of the difference between the two). For privacy reasons cross-site prefetches will currently only work if the user has no cookies set for the destination site — we don't want sites to be able to track user activity via prefetched pages (which they may never even actually visit) based on previously-set cookies.
Note: In the future an opt-in will be provided via the Supports-Loading-Mode
header, but this was not implemented at the time of writing.
For browsers that support it, speculation rules prefetch should be preferred over older prefetch mechanisms, namely <link rel="prefetch">
and fetch()
with a priority: "low"
option set on it. Because we know that speculation rules prefetch is for navigations, not general resource prefetching:
- It can be used for cross-site navigations, whereas
<link rel="prefetch">
cannot. - It doesn't get blocked by Cache-Control headers, whereas
<link rel="prefetch">
often does.
In addition, speculation rules prefetch:
- Automatically lowers the priority when needed (
fetch()
doesn't). - Is respectful of the user's configuration. For example, prefetching doesn't happen when the user's device is in Battery Saver or Data Saver mode.
- Stores the prefetched resources in a per-document in-memory cache as opposed to the HTTP cache, which may result in slightly faster prefetching.
Using prerendering
Including prerender
rules inside <script type=speculationrules> ... </script>
will cause supporting browsers to fetch, render, and load the content into an invisible tab, stored in a per-document in-memory cache. This includes loading all subresources, running all JavaScript, and even loading subresources and performing data fetches started by JavaScript. Any cached prerenders and their subresources are discarded when you navigate away from the current page, except of course a prerendered document that you then navigate to.
Future navigations to a prerendered page will be near-instant. The browser activates the invisible tab instead of carrying out the usual navigation process, replacing the old foreground page with the prerendered page. If a page is activated before it has fully prerendered, it is activated in its current state and then continues to load, which means you will still see a significant performance improvement.
Prerendering uses memory and network bandwidth. If you prerender something the user doesn't navigate to, these are wasted (although the result may populate the HTTP cache if headers allow, allowing later use). The upfront cost of a prerender is much larger than the upfront cost of a prefetch, and there are more conditions that could make content unsafe to prerender (see Unsafe speculative loading conditions for more details). As a result, you are encouraged to adopt prerendering more sparingly, carefully considering cases where there is a high likelihood of the page being navigated to, and you think the user experience benefit is worth the extra cost.
Note: To put the amount of potential resource wastage in perspective, a prerender uses about the same amount of resources as rendering an <iframe>
.
Prerendering is restricted to same-origin documents by default. Cross-origin, same-site prerendering is possible — it requires the navigation target to opt-in using the Supports-Loading-Mode
header with a value of credentialed-prerender
. Cross-site prerendering is not possible at this time.
For browsers that support it, speculation rules prerender should be preferred over older prerender mechanisms, namely <link rel="prerender">
:
-
<link rel="prerender">
is Chrome-specific and was never standardized, and the Chrome engineering team are in the process of sunsetting it. - It loads subresources loaded via JavaScript, whereas
<link rel="prerender">
doesn't. - It doesn't get blocked by Cache-Control settings, whereas
<link rel="prerender">
often does. - Speculation rules prerender should be treated as a hint and a progressive enhancement. Unlike
<link rel="prerender">
, it is a speculative hint and the browser may choose not to act upon the hint based on user settings, current memory usage, or other heuristics.
Speculation rules API feature detection
You can check if the Speculation Rules API is supported using the following code:
if (
HTMLScriptElement.supports &&
HTMLScriptElement.supports("speculationrules")
) {
console.log("Your browser supports the Speculation Rules API.");
}
For example, you might want to insert speculation rules for prefetching in supporting browsers, but use an older technology such as <link rel="prefetch">
in others:
if (
HTMLScriptElement.supports &&
HTMLScriptElement.supports("speculationrules")
) {
const specScript = document.createElement("script");
specScript.type = "speculationrules";
const specRules = {
prefetch: [
{
source: "list",
urls: ["/next.html"],
},
],
};
specScript.textContent = JSON.stringify(specRules);
document.body.append(specScript);
} else {
const linkElem = document.createElement("link");
linkElem.rel = "prefetch";
linkElem.href = "/next.html";
document.head.append(linkElem);
}
Detecting prefetched and prerendered pages
This section looks at different ways to detect whether a requested page has been prefetched or prerendered.
Server-side detection
Prefetched and prerendered page requests are sent with the Sec-Purpose request header:
For prefetch:
For prerender:
Servers can respond based on this header, for example to log speculative load requests, return different content, or even prevent the speculative loading from happening. If a non-success response code is returned (not 200 or 304), then the page will not be prefetched/prerendered. This is the easiest way to prevent speculative loading, although it is usually a better approach to allow the prefetch/prerender, but delay any actions that should only happen then the page is actually viewed, using JavaScript.
JavaScript prefetch detection
When a page is prefetched, its PerformanceResourceTiming.deliveryType
entry will return a value of "navigational-prefetch"
. You could use the following to run a function when a performance entry of type "navigational-prefetch"
is received:
if (
performance.getEntriesByType("navigation")[0].deliveryType ===
"navigational-prefetch"
) {
respondToPrefetch();
}
This technique is useful when measuring performance, or when you want to defer actions that might cause problems if they occur during prefetching (see Unsafe prefetching).
JavaScript prerender detection
To run an activity while the page is prerendering, you can check for the Document.prerendering
property. You could for example run some analytics:
if (document.prerendering) {
analytics.sendInfo("got this far during prerendering!");
}
When a prerendered document is activated, PerformanceNavigationTiming.activationStart
is set to a DOMHighResTimeStamp
representing the time between when the prerender was started and the document was actually activated. The following function can check for prerendering and prerendered pages:
function pagePrerendered() {
return (
document.prerendering ||
self.performance?.getEntriesByType?.("navigation")[0]?.activationStart > 0
);
}
When the prerendered page is activated by the user viewing the page, the prerenderingchange
event will fire. This can be used to enable activities that previously would be started by default on page load but which you wish to delay until the page is actually viewed by the user. The following code sets up an event listener to run a function once prerendering has finished, on a prerendered page, or runs it immediately on a non-prerendered page:
if (document.prerendering) {
document.addEventListener("prerenderingchange", initAnalytics, {
once: true,
});
} else {
initAnalytics();
}
Unsafe speculative loading conditions
This section covers conditions to look out for, under which prefetching and/or prerendering are unsafe. This means that prefetching/prerendering pages that exhibit these conditions may require mitigations in your code, or need to be avoided altogether.
Unsafe prefetching
As mentioned earlier, we recommend adopting prefetching broadly, as the risk to reward ratio is fairly low — the potential for resource wastage is minimal, and the performance improvements can be significant. However, you need to make sure prefetched pages do not cause problems with the flow of your application.
When a prefetch is done, the browser downloads the response body of the referenced page via a single GET request, which the user may navigate to at a future time. Problems can arise specifically when the URL of the request performs a server-initiated side effect that you don't want to happen until the URL is actually navigated to.
For example:
- Sign-out URLs.
- Language switching URLs.
- "Add to cart" URLs.
- Sign-in flow URLs where the server causes an SMS to be sent, for example as a one-time password (OTP).
- URLs that increment a user's usage allowance numbers, such as consuming their monthly free article allowance or starting the timer on their monthly minutes.
- URLs that initiate server-side ad conversion tracking.
Such issues can be mitigated on the server by watching for the Sec-Purpose: prefetch
header as the requests come in, and then running specific code to defer problematic functionality. Later on, when the page is actually navigated to, you can initiate the deferred functionality via JavaScript if needed.
If the functionality only occurs under normal circumstances when JavaScript runs, then prefetching is safe, since the JavaScript will not run until activation.
It is also potentially risky to prefetch a document whose server-rendered contents will change due to actions the user can take on the current page. This could include, for example, flash sale pages or movie theater seat maps. Test such cases carefully, and mitigate such issues by updating content once the page is loaded. See Server-rendered varying state for more details about these cases.
Note: Browsers will cache prefetched pages for a short time (Chrome for example caches them for for 5 minutes) before discarding them, so in any case, your users might see content that is up to 5 minutes out of date.
One final tip is to audit the URLs listed as disallowed in your Robots.txt
file — normally these URLs point to pages that can only be accessed by authenticated users, and therefore should not be included in search engine results. Many of these will be fine, but it can be a good place to find URLs unsafe for prefetching (i.e. they exhibit the conditions described above).
Unsafe prerendering
Prerendering is more risky to adopt than prefetching and should therefore be done more sparingly, in cases where it is worth it. There are more unsafe conditions to watch out for with prerendering so, while the reward is higher, the risk is too.
When a prerender is done, the browser GETs the URL and renders and loads the content into an invisible tab. This includes running the content's JavaScript and loading all subresources, including those fetched by JavaScript. Content can be potentially unsafe to prerender if any of the following conditions are observed:
- The URL is unsafe to prefetch. Read the previous section first if you haven't already, and understand that these conditions also equally apply to unsafe prerendering.
- The page's JavaScript modifies client-side storage (for example Web Storage or IndexedDB) on load in a way that may cause confusing effects in other, non-prerendered pages that the user is currently looking at.
- 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. Again, this can affect the flow of the application, or cause incorrect performance or usage reporting. See Server-rendered varying state for more details about such use cases.
To mitigate such problems, you can use the following techniques:
- Watch for the
Sec-Purpose: prerender
header on the server as the requests come in, and then run specific code to defer problematic functionality. - Use the
prerenderingchange
event to detect when the prerendered page is actually activated and run code as a result. This is useful in two cases: - Deferring code that may cause problems if it is run before the page is viewed. For example, you may want to wait until after activation to update client-side storage or modify server-side state using JavaScript. This can avoid situations when the UI and the application state become out of sync with one another, for example a shopping cart showing no items even though the user has added some.
- If the above is not possible, then you could still rerun code after the page is activated to bring the app up-to-date again. For example, a highly-dynamic flash sale page might rely on content updates coming in from a third-party library. If you can't delay the updates, you can always get fresh updates once the user views the page. Prerendered pages can be updated in real time using the Broadcast Channel API, or another mechanism such as
fetch()
or a WebSocket
. This guarantees that the user will see up-to-date content after prerendering activation.
- Manage your third-party analytics scripts carefully — if possible, use scripts that are prerendering-aware (for example use the
Document.prerendering
property to defer running on prerendering pages) such as Google Analytics or NewRelic. - Note that cross-origin
<iframe>
loads are delayed while prerendering, therefore most other third-party widgets such as adtech are actually safe to use while prerendering. - For third-party scripts that are not prerendering-aware, avoid loading them until after activation using the
prerenderingchange
event, as mentioned earlier.
Server-rendered varying state
There are two main types of server-rendered state to be concerned with: outdated state, and user-specific state. This can cause both unsafe prefetching and prerendering.
- Outdated state: Consider the example of a server-rendered list of blog comments, which may become out of date between the blog post being prerendered, and it being viewed. This might be particularly problematic if the current page is an admin panel where the user is deleting spam comments. If the user then navigates to the blog post, they might be confused as to why they can see the spam comments they just deleted.
- User-specific state: Consider the example of tracking sign-in state via a cookie. Problems can arise like the following:
- The user visits
https://site.example/a
in tab 1 and https://site.example/b
in tab 2, while logged out. -
https://site.example/b
prerenders https://site.example/c
. It will be prerendered in a logged-out state. - The user signs in to
https://site.example
in tab 1. - The user switches to tab 2 and clicks the link to
https://site.example/c
, which activates the prerendered page. - Tab 2 displays a signed-out view of
https://site.example/c
, which confuses the user since they thought they were logged in.
User-specific state problems can occur for other user settings, for example language settings, dark-mode preferences, or adding items to a cart. They can also occur when only a single tab is involved:
- Let's say the user visits
https://site.example/product
. -
https://site.example.com/product
prerenders https://site.example.com/cart
. It prerenders with 0 items in the cart. - The user clicks on the "Add to cart" buttons, which initiates a fetch request to add the item to the user's cart (with no page reload).
- The user clicks on the link to
https://site.example.com/cart
, which activates the prerendered page. - The user sees an empty cart, even though they just added something to it.
The best mitigation for these cases, and indeed any time when content can get out of sync with the server, is for pages to refresh themselves as needed. For example, a server might use the Broadcast Channel API, or another mechanism such as fetch()
or a WebSocket
. Pages can then update themselves appropriately, including speculatively loaded pages that have not yet activated.
Session history behavior for prerendered documents
Activating a prerendering/prerendered document behaves like any conventional navigation, from the end-user perspective. The activated document is displayed in the tab and appended to session history, and any existing forward history entries are pruned. Any navigations taking place within the prerendering browsing context before activation do not affect the session history.
From the developer's perspective, a prerendering document can be thought of as having a trivial session history where only one entry — the current entry — exists. All navigations within the prerendering context are effectively replaced.
While API features that operate on session history (for example History
and Navigation
) can be called within prerendering documents, they only operate on the context's trivial session history. Consequently, prerendering documents do not take part in their referring page's joint session history. For example, they cannot navigate their referrer via History.back()
.
This design ensures that users get the expected experience when using the back button — i.e. that they are taken back to the last thing they saw. Once a prerendering document is activated, only a single session history entry gets appended to the joint session history, ignoring any previous navigations that happened within the prerendering browsing context. Going back one step in the joint session history — for example by pressing the back button — takes the user back to the referrer page.
Because a prerendered page is opened in a hidden state, a number of APIs and other web platform features that cause potentially intrusive behaviors are not activated in this state, and are instead deferred until the page is activated or restricted altogether. In the small number of cases where this is not yet possible, the prerender is canceled.
Asynchronous API deferral
The following asynchronous features' results are deferred in prerendered documents until they are activated:
-
Audio Output Devices API:
MediaDevices.selectAudioOutput()
-
Background Fetch API:
BackgroundFetchManager.fetch()
-
Broadcast Channel API:
BroadcastChannel.postMessage()
-
Credential Management API:
CredentialsContainer.create()
, CredentialsContainer.get()
, CredentialsContainer.store()
-
Encrypted Media Extensions API:
Navigator.requestMediaKeySystemAccess()
-
Gamepad API:
Navigator.getGamepads()
, gamepadconnected
event, gamepaddisconnected
event -
Geolocation API:
Geolocation.getCurrentPosition()
, Geolocation.watchPosition()
, Geolocation.clearWatch()
-
HTMLMediaElement
API: The playback position will not advance while the containing document is prerendering -
Idle Detection API:
IdleDetector.start()
-
Media Capture and Streams API:
MediaDevices.getUserMedia()
(and the legacy Navigator.getUserMedia()
version), MediaDevices.enumerateDevices()
-
Notifications API:
Notification()
constructor, Notification.requestPermission()
-
Push API:
PushManager.subscribe()
-
Screen Orientation API:
ScreenOrientation.lock()
, ScreenOrientation.unlock()
-
Sensor APIs:
Sensor.start()
-
Service Worker API:
ServiceWorker.postMessage()
, ServiceWorkerContainer.register()
, ServiceWorkerRegistration.update()
, ServiceWorkerRegistration.unregister()
-
Storage API:
StorageManager.persist()
-
Web Audio API:
AudioContext
s are not allowed to start while the containing document is prerendering -
Web Bluetooth API:
Bluetooth.getDevices()
, Bluetooth.requestDevice()
-
WebHID API:
HID.getDevices()
, HID.requestDevice()
-
Web Locks API:
LockManager.query()
, LockManager.request()
-
Web MIDI API:
Navigator.requestMIDIAccess()
-
Web NFC API:
NDefReader.write()
, NDefReader.scan()
-
Web Serial API:
Serial.getPorts()
, Serial.requestPort()
-
Web Speech API:
SpeechRecognition.abort()
, SpeechRecognition.start()
, SpeechRecognition.stop()
, SpeechSynthesis.cancel()
, SpeechSynthesis.pause()
, SpeechSynthesis.resume()
, SpeechSynthesis.speak()
-
WebUSB API:
USB.getDevices()
, USB.requestDevice()
-
WebXR Device API:
XRSystem.requestSession()
Implicitly restricted APIs
The following features will automatically fail or no-op in documents that are not activated.
APIs that require transient activation or sticky activation:
APIs that require the containing document to be focused:
APIs that require the containing document's Document.visibilityState
to be "visible"
:
Other restricted features
- Download links, i.e.
<a>
and <area>
elements with the download
attribute, will have their downloads delayed until prerendering has finished. - No cross-site navigations: Any prerendering document that navigates to a different site will be immediately discarded before a request to that other site is sent.
- Restricted URLs: Prerendering documents cannot host non-HTTP(S) top-level URLs. Including the following URL types will cause the prerender to be immediately discarded:
-
javascript:
URLs -
data:
URLs -
blob:
URLs -
about:
URLs, including about:blank
and about:srcdoc
- Session storage:
Window.sessionStorage
can be used, but the behavior is very specific, to avoid breaking sites that expect only one page to access the tab's session storage at a time. A prerendered page therefore starts out with a clone of the tab's session storage state from when it was created. Upon activation, the prerendered page's storage clone is discarded, and the tab's main storage state is used instead. Pages that use session storage can use the prerenderingchange
event to detect when this storage swap occurs. -
Window.print()
: Any calls to this method are ignored. - "Simple dialog methods" are restricted as follows:
- Dedicated/shared worker scripts are loaded, but their execution is deferred until the prerendered document is activated.
- Cross-origin
<iframe>
loads are delayed while prerendering until after the page is activated.
Interfaces
The Speculation Rules API does not define any interfaces of its own.
Extensions to other interfaces
Document.prerendering
-
A boolean property that returns true
if the document is currently in the process of prerendering.
-
prerenderingchange
event -
Fired on a prerendered document when it is activated (i.e. the user views the page).
PerformanceNavigationTiming.activationStart
-
A number representing the time between when a document starts prerendering and when it is activated.
-
PerformanceResourceTiming.deliveryType
, "navigational-prefetch"
value -
Signals that the type of a performance entry is a prefetch.
-
Content-Security-Policy
'inline-speculation-rules'
value -
Used to opt-in to allowing usage of <script type="speculationrules">
to define speculation rules on the document being fetched.
Supports-Loading-Mode
-
Set by a navigation target to opt-in to using various higher-risk loading modes. For example, cross-origin, same-site prerendering requires a Supports-Loading-Mode
value of credentialed-prerender
.
HTML features
<script type="speculationrules"> ... </script>
-
Used to define a set of prefetch and/or prerender speculation rules on the current document.
Examples
Specifications
Browser compatibility
|
Desktop |
Mobile |
|
Chrome |
Edge |
Firefox |
Internet Explorer |
Opera |
Safari |
WebView Android |
Chrome Android |
Firefox for Android |
Opera Android |
Safari on IOS |
Samsung Internet |
Speculation_Rules_API |
109105Initial support included same-origin prerendering only.
|
109105Initial support included same-origin prerendering only.
|
No |
No |
9591Initial support included same-origin prerendering only.
|
No |
109103Initial support included same-origin prerendering only.
|
109103Initial support included same-origin prerendering only.
|
No |
7471Initial support included same-origin prerendering only.
|
No |
21.020.0Initial support included same-origin prerendering only.
|
prefetch |
110 |
110 |
No |
No |
96 |
No |
103 |
103 |
No |
71 |
No |
20.0 |
referrer_policy |
111 |
111 |
No |
No |
97 |
No |
111 |
111 |
No |
No |
No |
22.0 |
requires |
110 |
110 |
No |
No |
96 |
No |
103 |
103 |
No |
71 |
No |
20.0 |
See also