Speculation Rules API — Lightning-Fast Web Navigation with Prefetch and Prerender
Posted on: 4/18/2026 1:09:29 PM
Table of contents
- 1. What Is the Speculation Rules API?
- 2. Prefetch vs Prerender — Choosing the Right Strategy
- 3. JSON Syntax and How to Implement
- 4. Eagerness — Controlling When Speculation Triggers
- 5. Chrome Limits and Safeguards
- 6. Detection and Measurement
- 7. Handling Dangerous Situations
- 8. Deploying to Different Site Types
- 9. Impact on Core Web Vitals
- 10. New Features: Tag and Target Hint
- 11. Content Security Policy
- 12. Browser Support and Progressive Enhancement
- 13. Deployment Checklist
- Conclusion
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.
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
2. Prefetch vs Prerender — Choosing the Right Strategy
These two strategies differ significantly in cost and effectiveness:
| Criterion | Prefetch | Prerender |
|---|---|---|
| Behavior | Downloads the response body (HTML) | Downloads + fully renders in a hidden tab |
| Subresources (CSS, JS, images) | Not fetched | All fetched |
| JavaScript | Not executed | Fully executed |
| Memory cost | Low | High (similar to an iframe) |
| Speed boost | Moderate — saves a network round-trip | Near-instant — the page is already ready |
| Cross-origin | Same-site (no cookies) | Same-origin by default |
| Cache | In-memory, 5 minutes | In-memory, 5 minutes |
| Use when | Many pages might be visited | Pages 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:
| Eagerness | Desktop | Mobile | Best for |
|---|---|---|---|
| immediate | Right as the rules are parsed | Guaranteed URLs (homepage → dashboard) | |
| eager | Hover 10ms | Link in viewport 50ms | Primary menu navigation |
| moderate | Hover 200ms or pointerdown | Scroll stop + viewport heuristic 500ms | Links within article content |
| conservative | Pointer down / touch start | Heavy 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
5. Chrome Limits and Safeguards
Chrome enforces limits to prevent resource abuse:
| Eagerness | Max prefetches | Max prerenders | Behavior when full |
|---|---|---|---|
| immediate / eager | 50 | 10 | Reject new |
| moderate / conservative | 2 | 2 | FIFO — 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;prerenderServers 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
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:
💡 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
| Browser | Prefetch | Prerender | Document Rules | Eagerness |
|---|---|---|---|---|
| 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:
- Identify safe URLs: Exclude logout, checkout, add-to-cart, and anything with side effects
- Choose the right eagerness:
moderatefor most cases,immediatefor guaranteed navigation - Defer analytics: Use the
prerenderingchangeevent so prerender isn't counted as a real page view - Check Sec-Purpose server-side: Skip side effects for speculation requests
- Add CSP directive:
'inline-speculation-rules'if your site uses Content Security Policy - Test in Chrome DevTools: Check Application → Speculative loads for prerender status
- 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:
Change Data Capture with Debezium — Real-Time Data Sync for Microservices
gRPC and Protocol Buffers on .NET 10 — High-Performance Microservice Communication
Disclaimer: The opinions expressed in this blog are solely my own and do not reflect the views or opinions of my employer or any affiliated organizations. The content provided is for informational and educational purposes only and should not be taken as professional advice. While I strive to provide accurate and up-to-date information, I make no warranties or guarantees about the completeness, reliability, or accuracy of the content. Readers are encouraged to verify the information and seek independent advice as needed. I disclaim any liability for decisions or actions taken based on the content of this blog.