docs.rodeo

MDN Web Docs mirror

Speculation Rules API

{{SeeCompatTable}} {{DefaultAPISidebar("Speculation Rules API")}} 

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 can be specified inside inline <script type="speculationrules"> elements and external text files referenced by the {{httpheader("Speculation-Rules")}}  response header. The rules are specified as a JSON structure.

A script example:

<script type="speculationrules">
  {
    "prerender": [
      {
        "where": {
          "and": [
            { "href_matches": "/*" },
            { "not": { "href_matches": "/logout" } },
            { "not": { "href_matches": "/*\\?*(^|&)add-to-cart=*" } },
            { "not": { "selector_matches": ".no-prerender" } },
            { "not": { "selector_matches": "[rel~=nofollow]" } }
          ]
        }
      }
    ],
    "prefetch": [
      {
        "urls": ["next.html", "next2.html"],
        "requires": ["anonymous-client-ip-when-cross-origin"],
        "referrer_policy": "no-referrer"
      }
    ]
  }
</script>

Speculation rules using a <script> element need to be explicitly allowed in the {{httpheader("Content-Security-Policy")}}  script-src directive if the site includes it. This is done by adding one of the 'inline-speculation-rules' source, a hash-source, or nonce-source.

An HTTP header example:

Speculation-Rules: "/rules/prefetch.json"

The text resource containing the speculation rules JSON can have any valid name and extension, but it must be served with an application/speculationrules+json MIME type.

[!NOTE] Rules can be specified using both an inline script and the HTTP header simultaneously — all rules applied to a document are parsed and added to the document’s speculation rules list.

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 {{httpheader("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.

Using prefetching

Including prefetch rules inside a <script type="speculationrules"> element or Speculation-Rules header 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 for cross-site prefetches will be provided via the {{httpheader("Supports-Loading-Mode")}}  header, but this was not implemented at the time of writing (only cross-origin, same-site prerendering opt-in was available).

For browsers that support it, speculation rules prefetch should be preferred over older prefetch mechanisms, namely <link rel="prefetch"> and {{domxref("Window/fetch", "fetch()")}}  with a priority: "low" option set on it. Because we know that speculation rules prefetch is for navigations, not general resource prefetching:

In addition, speculation rules prefetch:

Using prerendering

Including prerender rules inside a <script type="speculationrules"> element or Speculation-Rules header 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 other conditions could also 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 {{htmlelement("iframe")}} .

[!NOTE] Many APIs will be automatically deferred when prerendering/until activation. See Platform features deferred or restricted during prerender for more details.

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 {{httpheader("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">:

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 {{httpheader("Sec-Purpose")}}  request header:

For prefetch:

Sec-Purpose: prefetch

For prerender:

Sec-Purpose: prefetch;prerender

Servers can respond based on this header, for example, to log speculative load requests, return different content, or even prevent speculative loading from happening. If a non-success response code is returned (any HTTP status other than in the 200-299 range after redirects), then the page will not be prefetched/prerendered. In addition the 204 and 205 status codes also prevent prerendering (but do not prevent prefetch).

Using a non-success code (for example a 503) is the easiest way to prevent speculative loading server-side, although it is usually a better approach to allow the prefetch/prerender, and use JavaScript to delay any actions that should only happen when the page is actually viewed.

JavaScript prefetch detection

When a page is prefetched, its {{domxref("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(); // Author-defined function
}

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 {{domxref("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, {{domxref("PerformanceNavigationTiming.activationStart")}}  is set to a {{domxref("DOMHighResTimeStamp")}}  representing the time between when the prerender was started and the document was 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 {{domxref("Document.prerenderingchange_event", "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 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 navigated to.

For example:

Such issues can be mitigated on the server by watching for the {{httpheader("Sec-Purpose", "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.

[!NOTE] You can find more details about the detection code in the Detecting prefetched and prerendered pages section.

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 5 minutes) before discarding them, so in any case, your users might see content that is up to 5 minutes out of date.

Prefetching is safe if all side effects of fetching the page result from JavaScript execution, since the JavaScript will not run until activation.

One final tip is to audit the URLs listed as disallowed in your {{glossary("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 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:

To mitigate such problems, you can use the following techniques:

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.

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:

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 {{domxref("Window/fetch", "fetch()")}}  or a {{domxref("WebSocket")}} . Pages can then update themselves appropriately, including speculatively loaded pages that have not yet been 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 {{domxref("History")}}  and {{domxref("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 {{domxref("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.

Platform features deferred or restricted during prerender

Because a prerendered page is opened in a hidden state, several APIs features that cause potentially intrusive behaviors are not activated in this state, and are instead deferred until the page is activated. Other web platform features that are problematic when prerendering are restricted altogether. This section provides details of what features are deferred or restricted.

[!NOTE] In the small number of cases where deferring and restricting are not possible, the prerender is canceled.

Asynchronous API deferral

Deferring means that the API feature immediately returns a pending promise and then does nothing until page activation. After activation, the feature runs as normal and the promise is resolved or rejected as normal.

The following asynchronous features’ results are deferred in prerendered documents until they are activated:

Implicitly restricted APIs

The following features will automatically fail or no-op in documents that are not activated.

APIs that require {{glossary("transient activation")}}  or {{glossary("sticky activation")}} :

APIs that require the containing document to be focused:

APIs that require the containing document’s {{domxref("Document.visibilityState")}}  to be "visible":

Other restricted features

Interfaces

The Speculation Rules API does not define any interfaces of its own.

Extensions to other interfaces

HTTP headers

HTML features

Examples

You can find a complete prerender demo here.

Specifications

{{Specifications}} 

Browser compatibility

{{Compat}} 

See also

In this article

View on MDN