LEARNING OBJECTIVES ⌵
- Understand why legacy
<link rel="prerender">was deprecated and replaced by the Speculation Rules API. - Implement declarative JSON speculation rules using
<script type="speculationrules">. - Configure speculation actions (
prefetchvsprerender) with URL list rules and dynamic document rules (whereselectors). - Master the 4 speculation eagerness levels:
immediate,eager,moderate, andconservative. - Manage the prerender lifecycle in JavaScript using
document.prerenderingand theprerenderingchangeevent.
📖 The Mental Model & Story (Intuitive Foundation)
Imagine an Olympic track relay race.
In a traditional relay, Runner 1 sprints the full 100 meters to the handover zone. Runner 2 stands completely still with feet planted on the track. When Runner 1 reaches the line and slaps the baton into Runner 2's hand, Runner 2 must accelerate from 0 km/h up to top speed. That acceleration phase loses valuable tenths of a second.
Now imagine a Flying Start. While Runner 1 is still 20 meters away, Runner 2 starts sprinting forward in the acceleration zone. By the exact moment the baton connects, Runner 2 is already moving at top speed (35 km/h). The handover is completely seamless, with zero velocity lost.
The Speculation Rules API is the browser's Flying Start:
- Instead of waiting for a user to click a link before requesting HTML, CSS, JavaScript, and images, the browser creates an invisible, background browsing context (a hidden tab).
- It downloads the full page, builds the DOM and CSSOM, executes scripts, and lays out the entire page in GPU memory.
- When the user clicks the link, the browser activates the pre-rendered page instantly. Navigation time drops from 800ms down to 0 milliseconds (Instant Page Load).
Technical Deep Dive & Specifications
The Fall of Legacy <link rel="prerender">
In early HTML5, developers used:
<!-- ⚠️ DEPRECATED / LEGACY NO-OP IN MODERN BROWSERS -->
<link rel="prerender" href="/next-page.html">
Legacy prerendering had critical architectural flaws:
- Uncontrolled Resource Spikes: It lacked fine-grained rules, crashing mobile devices by spawning heavy background processes.
- Double Analytics Hits: Background prerenders counted as real page views on servers even if the user never clicked the link.
- No Intent Detection: It executed unconditionally without knowing if the user hovered, touched, or scrolled near the link.
Consequently, modern browser engines deprecated legacy <link rel="prerender"> (downgrading it to a basic No-Store prefetch or ignoring it entirely) and designed the Speculation Rules API.
The Architecture of the Speculation Rules API
The modern Speculation Rules API is declared via an inline JSON script with type="speculationrules":
<script type="speculationrules">
{
"prerender": [
{
"source": "list",
"urls": ["/checkout", "/account"]
},
{
"source": "document",
"where": {
"and": [
{ "href_matches": "/articles/*" },
{ "not": { "selector_matches": ".no-prerender" } }
]
},
"eagerness": "moderate"
}
]
}
</script>
+----------------------------------------------------------------------------------------------------+
| SPECULATION RULES JSON SCHEMA TAXONOMY |
+----------------------------------------------------------------------------------------------------+
| Field | Allowed Values | Technical Function |
|--------------------|------------------------------|------------------------------------------------|
| **Action** | `"prerender"`, `"prefetch"` | Whether to fully render page or only pre-cache.|
| **source** | `"list"`, `"document"` | Explicit array of URLs or CSS document rules. |
| **urls** | Array of URL strings | Required when `source: "list"`. |
| **where** | Condition Object | Filtering logic for `source: "document"`. |
| `href_matches` | Pattern string / Glob | URL pattern (e.g. `"/blog/*"`, `"/product/*"`).|
| `selector_matches` | CSS Selector string | DOM selector on `<a>` tags (e.g. `".quick"`). |
| **eagerness** | `immediate`, `eager`, | Trigger threshold policy (see matrix below). |
| | `moderate`, `conservative` | |
| **target_hint** | `"_self"`, `"_blank"` | Context target for activation. |
+----------------------------------------------------------------------------------------------------+
Eagerness Threshold Matrix
The eagerness property prevents wasted bandwidth by tying speculation triggers to real user intent:
| Eagerness Level | Trigger Condition | Ideal Scenario | Network / CPU Cost |
|---|---|---|---|
"immediate" |
Triggers as soon as the Speculation Rules script is parsed. | Fixed multi-step funnel with >90% conversion probability. | High |
"eager" |
Triggers with minimal delay once the link is in the DOM. | High-confidence navigation links (e.g. primary CTA button). | High |
"moderate" |
Triggers on 200ms pointer hover (mouseover) or pointer down (touchstart/pointerdown). |
Blog lists, product grids, search results. | Low / Optimal ⚡ |
"conservative" |
Triggers only on pointer down / mouse click initiation (mousedown). | Complex, data-heavy sub-pages with uncertain clicks. | Minimal |
The Prerender Lifecycle & Restricted APIs
When a document is prerendering in the background, the browser strictly isolates it to protect user privacy and avoid disruptive behavior:
[Hidden Prerender Pipeline]
1. HTML Streamed -> DOM Built -> CSSOM Parsed -> JavaScript Executed -> Render Tree Boxed.
2. SENSITIVE APIS RESTRICTED (Audio, Video Autoplay, Notifications, Geolocation, alert(), prompt()).
3. document.prerendering === true
|
[User Clicks Link on Active Page]
|
v
[Instant Page Activation]
1. Hidden tab swapped into primary viewport (0ms Paint Time!).
2. 'prerenderingchange' Event fires on Document.
3. document.prerendering becomes false.
4. Paused analytics, audio streams, and interactive timers resume.
JavaScript Lifecycle Handling Code Pattern
// Check if the current page is being executed inside a hidden prerender tab
if (document.prerendering) {
console.log('Page is prerendering in background... Delaying analytics beacon.');
document.addEventListener('prerenderingchange', () => {
console.log('Page ACTIVATED by user! Firing analytics beacon now.');
sendAnalyticsPageView();
}, { once: true });
} else {
// Standard direct navigation
sendAnalyticsPageView();
}
💻 Interactive Code Playground
Starter Code
Line-by-Line Code Breakdown
- Lines 8–22: Declares speculation rules using
<script type="speculationrules">."source": "document"tells the browser to monitor all<a>anchor links rendered in the DOM.where.andspecifies that any link matching/article/*that does not possess the.externalclass should be prerendered."eagerness": "moderate"instructs the browser to begin prerendering as soon as the user's cursor hovers over the article link for at least 200ms or on touchscreen pointer down.
- Lines 31 & 36: Internal article links that qualify for automatic background prerendering.
- Line 41: External link with
class="external", automatically excluded from prerendering. - Lines 49–62: JavaScript lifecycle monitor inspecting
performance.getEntriesByType('navigation')[0].activationStart. If non-zero, the page was loaded from a pre-rendered background process with 0ms visual latency!
Expected Browser Render Output (DevTools Speculative Loads Tab)
Opening Chrome DevTools $\longrightarrow$ Application $\longrightarrow$ Speculative Loads:
+----------------------------------------------------------------------------------------------------+
| Speculative Loads Inspector |
+----------------------------------------------------------------------------------------------------+
| URL | Action | Eagerness | Status | Discard Reason |
|-----------------------------------|------------|-----------|-----------|---------------------------|
| /article/quantum-computing | Prerender | Moderate | Ready | - (Hovered for 200ms) |
| /article/web-performance | Prerender | Moderate | Ready | - (Touchstart detected) |
| https://external-news.com | - | - | Filtered | Excluded by selector rule |
+----------------------------------------------------------------------------------------------------+
* User clicks link -> Status changes to 'Activated' -> LCP = 0.00ms 🚀🏋️ Hands-On Exercise
🎯 The Challenge: Implement High-Conversion Speculation Rules
You are the lead performance architect for a documentation platform (https://docs.devplatform.io).
Users frequently click through next/previous chapter buttons and sidebar links, but bounce rate increases if page transitions take longer than 400ms.
Requirements:
- Write a
<script type="speculationrules">block that:- Immediately prerenders the dedicated
next-chapterlink (#next-chapter-btn). - Uses moderate eagerness to prerender all internal documentation links matching
/guide/*. - Uses conservative eagerness to prefetch (not prerender) heavy
/api/*reference documentation links. - Strictly excludes any link containing the class
.no-speculateorlogout.
- Immediately prerenders the dedicated
- Add the JavaScript snippet to ensure page analytics beacons (
trackPageView()) only execute when the page is actively visible to the user.
🏁 Starter Code Sandbox
⚠️ Common Pitfalls
- Prerendering Destructive or Stateful Endpoints: Never allow speculation rules to match actions like
/cart/clear,/auth/logout, or/delete-account. The background browser process will trigger those actions silently! - Double-Counting Analytics: If your analytics script executes on initial script evaluation without checking
document.prerendering, you will record thousands of ghost page views for links users never actually visited. - Overusing
eagerness: "immediate": Prerendering 5 pages immediately can consume hundreds of megabytes of RAM and saturate the CPU, freezing the user's active page. Limitimmediateto single, high-probability links.
💡 Pro Tips
- Dynamic Rule Injection: You can append speculation rules dynamically via JavaScript based on machine learning predictions or user interaction:
const specScript = document.createElement('script'); specScript.type = 'speculationrules'; specScript.textContent = JSON.stringify({ prerender: [{ source: 'list', urls: ['/checkout/step2'], eagerness: 'immediate' }] }); document.head.appendChild(specScript); - Inspect Activation Time in RUM: Capture
activationStartin your Real User Monitoring (RUM) metrics:const navEntry = performance.getEntriesByType('navigation')[0]; if (navEntry && navEntry.activationStart > 0) { console.log('Instant Activation Duration:', navEntry.activationStart); } - Verify Prerender Status in DevTools: Use Chrome DevTools $\longrightarrow$ Application $\longrightarrow$ Speculative Loads to view live speculation pipelines, activation history, and rejection reasons.
📌 Key Takeaways
- The Speculation Rules API replaces legacy
<link rel="prerender">with declarative JSON rules in<script type="speculationrules">. - Speculation rules support two core actions:
"prefetch"(network cache only) and"prerender"(full DOM/CSSOM/JS background rendering). - Four eagerness levels (
immediate,eager,moderate,conservative) link background work to real user intent. - Background prerendered pages have restricted APIs (no audio autoplay, no modal alerts, delayed geolocation).
- Always protect analytics tracking by checking
document.prerenderingand listening for theprerenderingchangeevent. - --