Chrome has brought back full page prerendering, allowing near-instant navigation with the Speculation Rules API. Here’s everything you need to know.
What is Prerendering?
Prerendering loads an entire page in an invisible background tab before the user clicks. When activated, the page appears instantly—often achieving zero LCP, reduced CLS, and better INP.
The old <link rel="prerender"> was replaced by NoState Prefetch (which only fetched resources). Now Chrome has full prerendering back, via the Speculation Rules API.
How Pages Get Prerendered
Four ways Chrome prerenders pages:
- Address bar predictions - When typing in the omnibox with >50% confidence
- Bookmark hover - Holding pointer over bookmark buttons
- Search engines - When instructed by search results pages
- Speculation Rules API - Sites tell Chrome which pages to prerender
The Speculation Rules API
Add JSON rules to tell Chrome what to prerender:
URL Lists
<script type="speculationrules">
{
"prerender": [
{
"urls": ["next.html", "next2.html"]
}
]
}
</script>
Document Rules
Match links automatically with selectors:
<script type="speculationrules">
{
"prerender": [
{
"where": {
"and": [
{ "href_matches": "/*" },
{ "not": { "href_matches": "/wp-admin" } },
{ "not": { "selector_matches": ".no-prerender" } }
]
}
}
]
}
</script>
Eagerness Levels
Control when speculation fires:
immediate- Prerender as soon as rules are observed (default for URL lists)eager- Desktop: 10ms hover; Mobile: 50ms after anchor enters viewportmoderate- Desktop: 200ms hover or pointerdown; Mobile: 500ms after scroll stops (default for document rules)conservative- Only on pointer/touch down
Recommended starter rule:
<script type="speculationrules">
{
"prerender": [
{
"where": { "href_matches": "/*" },
"eagerness": "moderate"
}
]
}
</script>
Prefetch Instead
Just fetch without full prerender:
<script type="speculationrules">
{
"prefetch": [
{
"urls": ["next.html"]
}
]
}
</script>
Unlike old <link rel="prefetch">, this processes documents properly and holds them in memory.
Chrome Limits
| Prefetch | Prerender | |
|---|---|---|
immediate | 50 | 10 |
eager/moderate/conservative | 2 (FIFO) | 2 (FIFO) |
Chrome won’t speculate when:
- Save-Data mode enabled
- Energy saver on low battery
- Low memory
- “Preload pages” setting disabled
- Background tabs
Implementation
Static (in HTML)
<head>
<script type="speculationrules">
{
"prerender": [
{
"where": { "href_matches": "/*" },
"eagerness": "moderate"
}
]
}
</script>
</head>
Dynamic (via JavaScript)
if (HTMLScriptElement.supports?.('speculationrules')) {
const specScript = document.createElement('script');
specScript.type = 'speculationrules';
specScript.textContent = JSON.stringify({
prerender: [
{
urls: ['/next.html'],
},
],
});
document.body.append(specScript);
}
Via HTTP Header (Chrome 121+)
Speculation-Rules: "/speculationrules.json"
JSON file must have correct MIME type:
Content-Type: application/speculationrules+json
Detecting Prerender
In JavaScript
// Check if currently prerendering
if (document.prerendering) {
// Delay analytics/side effects
}
// Wait for activation
document.addEventListener('prerenderingchange', () => {
// Page is now visible
});
// Check if page was prerendered
const wasPrerendered = performance.getEntriesByType('navigation')[0]?.activationStart > 0;
Server-Side
Prerendered requests include:
Sec-Purpose: prefetch;prerender
Return non-2XX to prevent prerender (though better to allow it and delay actions via JS).
Analytics Impact
Important: Delay analytics until activation to avoid counting prerendered pages as views.
const whenActivated = new Promise((resolve) => {
if (document.prerendering) {
document.addEventListener('prerenderingchange', resolve, { once: true });
} else {
resolve();
}
});
async function initAnalytics() {
await whenActivated;
gtag('config', 'G-12345678-1');
}
Google Analytics, Google Publisher Tag, and Adsense handle this automatically.
Track Prerender Success
// Custom dimension for prerendered navigations
const wasPrerendered = self.performance?.getEntriesByType('navigation')[0]?.activationStart > 0;
gtag('set', { dimension1: wasPrerendered });
Restrictions & Gotchas
- Same-origin by default - Cross-origin prerender needs
Supports-Loading-Mode: credentialed-prerenderheader - Cross-origin prefetch - Works for same-site, or different-site without cookies
- No SPAs - Only full page navigations, not soft navigations
- Content Security Policy - Need hash/nonce for speculation rules, or use
inline-speculation-rules
Advanced Features
Target Hint (Chrome 138+)
Prerender for target="_blank" links:
<script type="speculationrules">
{
"prerender": [
{
"target_hint": "_blank",
"urls": ["next.html"]
}
]
}
</script>
No-Vary-Search (Chrome 141+)
Reuse prerenders despite different URL parameters:
No-Vary-Search: params=("utm_content")
<script type="speculationrules">
{
"prefetch": [
{
"urls": ["/products"],
"expects_no_vary_search": "params=(\"id\")"
}
]
}
</script>
Canceling Speculations
Remove speculation rules to cancel, or use HTTP header:
Clear-Site-Data: prefetchCache, prerenderCache
Useful after state changes (login, add-to-cart).
Best Practices
- Start with prefetch - Test before full prerender
- Use
moderateeagerness - Good balance for most sites - Exclude admin pages - Use
href_matchesto filter out/admin,/checkout - Watch your limits - Only 2 hover-based prerenders at a time
- Delay side effects - Wait for activation before analytics, state updates
- Test thoroughly - Use Chrome DevTools Application panel to debug speculation rules
- Monitor hit rate - Track how many prerenders actually get used
Debugging
Chrome DevTools shows speculation rules in Application > Speculative loads panel. Test with:
// Console check
performance.getEntriesByType('navigation')[0].activationStart;
// Non-zero = was prerendered
View Chrome’s predictions:
chrome://predictors
Browser Support
Checkout Can I Use for the latest support status. As of now, it’s only in Chromium-based browsers (Chrome, Edge, Opera) and not in Firefox or Safari.
Real Impact
Sites see near-instant page loads. Even fast sites benefit from:
- Near-zero LCP (already loaded!)
- Reduced CLS (load CLS happens before view)
- Better INP (page loaded before interaction)
The cost is memory and bandwidth—use wisely. Don’t over-prerender.
Quick Start
Add this to start prerendering same-origin links on 200ms hover:
<script type="speculationrules">
{
"prerender": [
{
"where": { "href_matches": "/*" },
"eagerness": "moderate"
}
]
}
</script>
Then refine based on analytics and hit rates.
Resources: