10LOC
Chrome APIsadvanced

The Speculation Rules API to prerender the next likely page

Published July 2, 2027

const injectedUrls = new Set<string>();

export const prerenderOnHoverIntent = (link: HTMLAnchorElement, dwellMs = 200) => {
  if (!(globalThis as any).HTMLScriptElement?.supports?.("speculationrules")) return;
  let timer = 0;

  link.addEventListener("pointerenter", () => {
    timer = window.setTimeout(() => {
      if (injectedUrls.has(link.href)) return;
      injectedUrls.add(link.href);

      const script = document.createElement("script");
      script.type = "speculationrules";
      script.textContent = JSON.stringify({ prerender: [{ urls: [link.href], eagerness: "moderate" }] });
      document.body.append(script);
    }, dwellMs);
  });

  link.addEventListener("pointerleave", () => window.clearTimeout(timer));
};

What

The Speculation Rules API lets a page declare, via a <script type="speculationrules"> JSON block, which URLs the browser should prefetch or fully prerender in the background — so a click on a likely-next link resolves instantly, because the navigation already happened before the click landed.

Why it matters

<link rel="prefetch"> only fetches a URL's resources; it doesn't run JS or produce a rendered page, so clicking through still costs a real navigation's worth of parse and render time. The older, Chrome-only NoState Prefetch and <link rel="prerender"> attempted full prerendering but had no way to express confidence or match many URLs by pattern, and no knob for controlling how much of the page's resource budget to spend on speculation — prerendering every link on a page indiscriminately is wasteful and, at scale, actively harmful to real traffic. Speculation Rules replaces both: a rule can prefetch (cheap, resources only) or prerender (an actual invisible tab, fully rendered, with JS running) URLs matched by an explicit list or a where pattern across the whole document, gated by an eagerness level (immediate, eager, moderate, conservative) that controls how confident the browser needs to be about the next click before it spends the resources.

How it works

prerenderOnHoverIntent injects a targeted speculation rule only for a link the pointer has meaningfully dwelt on — not every link the cursor happens to cross:

  • HTMLScriptElement.supports("speculationrules") is the feature-detection check for this specific API — the site works identically either way, this is purely progressive enhancement.
  • On pointerenter, a short timer (dwellMs, defaulting to 200ms) starts; pointerleave clears it. Only a link the pointer stays over past that dwell time gets a rule injected — a fast mouse pass-through never triggers one.
  • The rule itself is built with document.createElement("script"), type = "speculationrules", and a JSON textContent — speculation rules don't have to be present in the initial HTML; injecting them dynamically, per interaction, is exactly the pattern intended for something like hover-based prerendering.
  • eagerness: "moderate" is deliberately conservative on top of the manual dwell timer — the browser still budgets a limited number of concurrent speculative prerenders across the whole page, and combining a real hover signal with a mid-tier eagerness avoids reserving that budget for every link a cursor merely grazes.
  • injectedUrls guards against injecting the same rule twice for a link that's hovered repeatedly; a browser that already has an equivalent rule active would otherwise just ignore the duplicate, but tracking it avoids the redundant DOM write.

Gotchas

  • Firefox has no implementation. Safari shipped support in Safari 26.2 but ships it disabled by default. In practice, this is a Chromium-only feature today — which is fine, since it's pure progressive enhancement and does nothing on unsupported browsers, but don't build UX or perceived-performance budgets that assume it fires everywhere.
  • Prerendering a cross-origin URL requires the target to opt in — same-site rules work by default, but a cross-origin target generally needs to serve a matching Speculation-Rules response header or otherwise signal it accepts being prerendered; you can't blanket-prerender arbitrary third-party pages.
  • A prerendered page's JS runs for real, in the background, before the user has "visited" it in any way they'd notice. Analytics or pageview beacons that fire on load will fire during prerendering unless you specifically check document.prerendering (or listen for the prerenderingchange event) and defer them until the page is actually activated.
  • Chrome is experimenting with a middle ground — a "prerender until script" mode that renders a page's HTML/CSS but withholds script execution — currently behind an origin trial, not something to build against as a stable feature yet.

Browser support: Chrome/Edge 105+ (prerender key), 110+ (prefetch key), with eagerness/where pattern matching from Chrome/Edge 121+. Safari 26.2 ships the API but disabled by default. Firefox: no support.

Related

  • <link rel="prefetch">/<link rel="preload"> — simpler, resource-only, universally supported; the fallback path for anything that needs to work everywhere.
  • The Navigation API — controls what happens during a navigation once it starts; complementary to this, not an alternative — it has no speculative-loading behavior of its own.