Speculation Rules API — Lightning-Fast Web Navigation with Prefetch and Prerender

Posted on: 4/18/2026 1:09:29 PM

Have you ever clicked a link and the new page appeared instantly — no loading, no white flash, no waiting? That's not magic — that's the Speculation Rules API in action. It's one of the most powerful browser APIs for web performance, letting a site "predict" where a user will go next and load that page before they even click.

~0msLCP when prerender succeeds
50Max concurrent prefetches
10Max concurrent prerenders
Chrome 109+Supported since

1. What Is the Speculation Rules API?

The Speculation Rules API replaces the deprecated <link rel="prerender">. It lets developers declare speculation rules in JSON, specifying which URLs should be prefetched (download response only) or prerendered (fully rendered in a hidden tab).

When the user actually navigates to a prerendered page, the browser only needs to swap the hidden tab into the main tab — the page appears almost instantly.

flowchart LR
    A["👤 User reads
current page"] --> B["📋 Speculation Rules
analyze links"] B --> C{"Prefetch or
Prerender?"} C -->|Prefetch| D["📥 Fetch response body
(no render)"] C -->|Prerender| E["🖥️ Fully render
in a hidden tab"] D --> F["⚡ User clicks →
faster load"] E --> G["⚡ User clicks →
instant display"] style A fill:#f8f9fa,stroke:#e94560,color:#2c3e50 style B fill:#e94560,stroke:#fff,color:#fff style C fill:#2c3e50,stroke:#fff,color:#fff style D fill:#f8f9fa,stroke:#4CAF50,color:#2c3e50 style E fill:#4CAF50,stroke:#fff,color:#fff style F fill:#f8f9fa,stroke:#e94560,color:#2c3e50 style G fill:#e94560,stroke:#fff,color:#fff
How the Speculation Rules API works

2. Prefetch vs Prerender — Choosing the Right Strategy

These two strategies differ significantly in cost and effectiveness:

CriterionPrefetchPrerender
BehaviorDownloads the response body (HTML)Downloads + fully renders in a hidden tab
Subresources (CSS, JS, images)Not fetchedAll fetched
JavaScriptNot executedFully executed
Memory costLowHigh (similar to an iframe)
Speed boostModerate — saves a network round-tripNear-instant — the page is already ready
Cross-originSame-site (no cookies)Same-origin by default
CacheIn-memory, 5 minutesIn-memory, 5 minutes
Use whenMany pages might be visitedPages almost certainly to be visited

💡 Golden Rule

Prefetch broadly, prerender narrowly. Prefetch many pages because the cost is low. Prerender only 1-2 pages you're confident (>50%) the user will actually navigate to.

3. JSON Syntax and How to Implement

Speculation rules are declared inside a <script type="speculationrules"> tag:

3.1. List Rules — Specifying Exact URLs

<script type="speculationrules"> { "prerender": [{ "urls": ["/products/hot-deal", "/cart"] }], "prefetch": [{ "urls": ["/blog", "/contact", "/about"] }] } </script>

List rules are great when you know exactly which URLs to preload — e.g., the cart page on e-commerce, or the next article on a blog.

3.2. Document Rules — Automatic by Selector and URL Pattern

<script type="speculationrules"> { "prerender": [{ "where": { "and": [ { "href_matches": "/*" }, { "not": { "href_matches": "/admin/*" } }, { "not": { "href_matches": "/logout" } }, { "not": { "href_matches": "/*?*(^|&)add-to-cart=*" } }, { "not": { "selector_matches": ".no-prerender" } }, { "not": { "selector_matches": "[rel~=nofollow]" } } ] }, "eagerness": "moderate" }] } </script>

Document rules are more powerful because they automatically apply to every link that matches the conditions. You don't need to list every URL — just exclude the ones that shouldn't be prerendered.

3.3. Declaring via HTTP Header

Starting in Chrome 121+, you can serve speculation rules via HTTP header instead of inline scripts:

Speculation-Rules: "/speculationrules.json"

The JSON file must be served with the MIME type application/speculationrules+json and pass CORS checks if cross-origin.

4. Eagerness — Controlling When Speculation Triggers

The eagerness property decides when the browser starts speculating:

EagernessDesktopMobileBest for
immediateRight as the rules are parsedGuaranteed URLs (homepage → dashboard)
eagerHover 10msLink in viewport 50msPrimary menu navigation
moderateHover 200ms or pointerdownScroll stop + viewport heuristic 500msLinks within article content
conservativePointer down / touch startHeavy pages, need resource savings

Defaults

List rules default to immediate — load as soon as parsed. Document rules default to conservative — only load when the user starts interacting (pointer down). For document rules, moderate is usually the best balance between speed and resources.

flowchart TD
    A["Speculation Rules
parsed"] --> B{"Eagerness?"} B -->|immediate| C["Load immediately"] B -->|eager| D["Hover 10ms (Desktop)
Viewport 50ms (Mobile)"] B -->|moderate| E["Hover 200ms (Desktop)
Scroll stop 500ms (Mobile)"] B -->|conservative| F["Pointer down /
Touch start"] C --> G["Prefetch / Prerender
starts"] D --> G E --> G F --> G style A fill:#e94560,stroke:#fff,color:#fff style B fill:#2c3e50,stroke:#fff,color:#fff style C fill:#4CAF50,stroke:#fff,color:#fff style D fill:#f8f9fa,stroke:#4CAF50,color:#2c3e50 style E fill:#f8f9fa,stroke:#e94560,color:#2c3e50 style F fill:#f8f9fa,stroke:#ff9800,color:#2c3e50 style G fill:#e94560,stroke:#fff,color:#fff
Eagerness levels and their activation timings

5. Chrome Limits and Safeguards

Chrome enforces limits to prevent resource abuse:

EagernessMax prefetchesMax prerendersBehavior when full
immediate / eager5010Reject new
moderate / conservative22FIFO — drop oldest

Additionally, Chrome will automatically block speculation when:

  • Save-Data is enabled — user is saving data
  • Energy Saver mode with low battery
  • Low memory — device doesn't have enough RAM
  • "Preload pages" is disabled in Chrome Settings
  • Tab is in the background — don't waste resources on tabs the user can't see

⚠️ Prerender is cancelled when

Pages using alert(), confirm(), prompt(), or requesting geolocation, camera, or notification permissions will have their prerender cancelled. These APIs are deferred (paused) until the page actually activates. Avoid placing them during initial load.

6. Detection and Measurement

6.1. Feature Detection

if (HTMLScriptElement.supports && HTMLScriptElement.supports("speculationrules")) { // The browser supports Speculation Rules const specScript = document.createElement("script"); specScript.type = "speculationrules"; specScript.textContent = JSON.stringify({ prefetch: [{ urls: ["/next-page"] }] }); document.body.append(specScript); } else { // Fallback: use <link rel="prefetch"> const link = document.createElement("link"); link.rel = "prefetch"; link.href = "/next-page"; document.head.append(link); }

6.2. Server-side Detection via the Sec-Purpose Header

Speculation requests include a Sec-Purpose header:

// Prefetch request Sec-Purpose: prefetch // Prerender request Sec-Purpose: prefetch;prerender

Servers can use this header to skip side effects — e.g., not record analytics or trigger email notifications for prefetch/prerender requests.

6.3. Client-side Detection

// Is the page currently in a prerendering state? if (document.prerendering) { // Defer analytics, mutations... document.addEventListener("prerenderingchange", () => { // Page has activated — run analytics gtag("event", "page_view", { prerendered: true }); }, { once: true }); } // Was this page prefetched? const navEntry = performance.getEntriesByType("navigation")[0]; if (navEntry.deliveryType === "navigational-prefetch") { console.log("This page was served from the prefetch cache"); } // Measure activation time for prerender if (navEntry.activationStart > 0) { const prerenderTime = navEntry.activationStart; console.log(`Prerender activation after ${prerenderTime}ms`); }

7. Handling Dangerous Situations

Not every URL should be prefetched/prerendered. Some URLs have side effects when requested:

flowchart TD
    A["🔗 URL needing
speculation"] --> B{"Any side
effects?"} B -->|No| C["✅ Safe
Prefetch/Prerender"] B -->|Yes| D{"Type of
side effect?"} D -->|Logout/Auth| E["❌ Exclude
from rules"] D -->|Analytics| F["⏸️ Defer
via prerenderingchange"] D -->|Add to cart| G["❌ Exclude
from rules"] D -->|Storage mutation| H["⏸️ Defer
via prerenderingchange"] style A fill:#f8f9fa,stroke:#e94560,color:#2c3e50 style B fill:#2c3e50,stroke:#fff,color:#fff style C fill:#4CAF50,stroke:#fff,color:#fff style D fill:#e94560,stroke:#fff,color:#fff style E fill:#ff9800,stroke:#fff,color:#fff style F fill:#f8f9fa,stroke:#4CAF50,color:#2c3e50 style G fill:#ff9800,stroke:#fff,color:#fff style H fill:#f8f9fa,stroke:#4CAF50,color:#2c3e50
Workflow for handling URLs with side effects

A safe pattern for Analytics

// Wait until the user actually sees the page, then track function initAnalytics() { gtag("config", "GA_ID"); gtag("event", "page_view"); } if (document.prerendering) { document.addEventListener("prerenderingchange", initAnalytics, { once: true }); } else { initAnalytics(); }

A server-side pattern (ASP.NET Core)

app.Use(async (context, next) => { var secPurpose = context.Request.Headers["Sec-Purpose"].ToString(); if (secPurpose.Contains("prefetch") || secPurpose.Contains("prerender")) { // Skip side effects for speculation requests context.Items["IsSpeculation"] = true; } await next(); }); // In a controller/endpoint: if (HttpContext.Items.ContainsKey("IsSpeculation")) { // Don't send emails, don't write audit logs return View(model); }

8. Deploying to Different Site Types

8.1. Blog / Content Site

<script type="speculationrules"> { "prerender": [{ "where": { "and": [ { "href_matches": "/blog/*" }, { "not": { "selector_matches": "[rel~=nofollow]" } } ] }, "eagerness": "moderate" }], "prefetch": [{ "where": { "href_matches": "/tag/*" }, "eagerness": "conservative" }] } </script>

8.2. E-commerce

<script type="speculationrules"> { "prerender": [{ "where": { "and": [ { "href_matches": "/products/*" }, { "not": { "href_matches": "/*?*add-to-cart=*" } }, { "not": { "href_matches": "/checkout*" } }, { "not": { "href_matches": "/payment*" } } ] }, "eagerness": "moderate" }], "prefetch": [{ "urls": ["/cart"], "eagerness": "conservative" }] } </script>

8.3. SPA with Vue.js / Nuxt — Combining with the Router

// plugins/speculation-rules.client.ts (Nuxt 3) export default defineNuxtPlugin(() => { if (!HTMLScriptElement.supports?.("speculationrules")) return; const router = useRouter(); router.afterEach((to) => { // Remove old rules document.querySelectorAll('script[type="speculationrules"]') .forEach(el => el.remove()); // Generate new rules based on the current route const rules = buildRulesForRoute(to.path); if (!rules) return; const script = document.createElement("script"); script.type = "speculationrules"; script.textContent = JSON.stringify(rules); document.body.append(script); }); }); function buildRulesForRoute(path: string) { // Blog: prerender the next article if (path.startsWith("/blog/")) { return { prerender: [{ where: { and: [ { selector_matches: ".next-post a, .related-posts a" } ] }, eagerness: "moderate" }] }; } // Homepage: prefetch primary sections if (path === "/") { return { prefetch: [{ where: { selector_matches: ".featured-links a" }, eagerness: "eager" }] }; } return null; }

9. Impact on Core Web Vitals

The Speculation Rules API dramatically improves Core Web Vitals when a page is successfully prerendered:

~0msLCP — the page is already rendered
0CLS — layout shifts happen before the user sees them
BetterINP — JS is already executed, ready to interact

💡 When prerender hasn't finished

If the user navigates before prerender is done, the page continues loading in the foreground — you still gain a head-start advantage over loading from scratch. It never ends up slower than the no-speculation case.

10. New Features: Tag and Target Hint

Tag Field (Chrome 136+)

Attach labels to rules for server-side filtering via CDN:

{ "tag": "product-prerender", "prerender": [{ "urls": ["/products/latest"] }] }

The server receives a Sec-Speculation-Tags: product-prerender header — the CDN can distinguish speculation requests from the site vs. from the browser's own automated ones.

Target Hint (Chrome 138+, experimental)

{ "prerender": [{ "urls": ["/external-page"], "target_hint": "_blank" }] }

Lets you prerender pages that will open in a new tab — useful for target="_blank" links.

11. Content Security Policy

If your site uses CSP, you need a directive for inline speculation rules:

Content-Security-Policy: script-src 'self' 'inline-speculation-rules'

Or use a nonce/hash as with regular scripts. Without this directive, inline <script type="speculationrules"> will be blocked.

12. Browser Support and Progressive Enhancement

BrowserPrefetchPrerenderDocument RulesEagerness
Chrome 109+✅ (121+)✅ (121+)
Edge 109+✅ (121+)✅ (121+)
Firefox
Safari⚠️ Behind flag⚠️ Behind flag

Progressive Enhancement — nothing breaks

The Speculation Rules API is pure progressive enhancement. Browsers that don't support it simply ignore <script type="speculationrules"> — no errors, no impact. You can deploy today without worrying about breaking the experience on Firefox or Safari.

13. Deployment Checklist

Before pushing Speculation Rules to production, check:

  1. Identify safe URLs: Exclude logout, checkout, add-to-cart, and anything with side effects
  2. Choose the right eagerness: moderate for most cases, immediate for guaranteed navigation
  3. Defer analytics: Use the prerenderingchange event so prerender isn't counted as a real page view
  4. Check Sec-Purpose server-side: Skip side effects for speculation requests
  5. Add CSP directive: 'inline-speculation-rules' if your site uses Content Security Policy
  6. Test in Chrome DevTools: Check Application → Speculative loads for prerender status
  7. Measure: Compare Core Web Vitals before/after using CrUX or RUM data

Conclusion

The Speculation Rules API marks a major leap in web performance — for the first time, developers have a native, simple-JSON-declarative tool to achieve near-instant navigation without complex frameworks or service workers. Because it's pure progressive enhancement, you can roll it out today and users on Chrome/Edge immediately benefit, while other browsers keep working as usual.

The most important thing: prefetch broadly, prerender narrowly, defer side effects — three principles that are enough to deploy it safely and effectively on any kind of website.

💡 Where to start?

Add a simple <script type="speculationrules"> tag with document rules + moderate eagerness for internal links. Just 10 lines of code, yet the next navigation's LCP can drop to near zero.

References: