Vai al contenuto principale
Web Performance

Speculation Rules API: Pre-rendering di Pagine Prima della Navigazione e Impatto Misurato sui Core Web Vitals

6 min lettura
LD
Lucio Durán
Engineering Manager & AI Solutions Architect
Disponibile anche in: English, Español

L'API: Come Funziona

Le speculation rules vengono dichiarate come oggetto JSON dentro un tag <script type="speculationrules">:

<script type="speculationrules">
{
 "prerender": [
 {
 "where": {
 "and": [
 { "href_matches": "/*" },
 { "not": { "href_matches": "/logout" } },
 { "not": { "href_matches": "/api/*" } },
 { "not": { "selector_matches": ".no-prerender" } }
 ]
 },
 "eagerness": "moderate"
 }
 ],
 "prefetch": [
 {
 "where": {
 "href_matches": "/blog/*"
 },
 "eagerness": "conservative"
 }
 ]
}
</script>

Questo dice a Chrome: "Per qualsiasi link interno che non sia la pagina logout o una route API, pre-renderizzalo quando l'utente mostra interesse moderato (hover per 200ms). Per i link del blog, prefetchali con intento conservativo (pointerdown — il momento del click)."

Livelli di Eagerness: Lo Spettro di Confidenza

Ci sono quattro livelli di eagerness, ognuno che mappa a un diverso segnale di intento utente:

| Eagerness | Trigger | Lead Time Tipico | Caso d'Uso | |-----------|---------|------------------|------------| | immediate | Caricamento pagina | Secondi | Navigazione successiva nota (passo 2 wizard) | | eager | Link diventa visibile | Secondi | Navigazione ad alta probabilità | | moderate | Hover (200ms) | 200-800ms | Link di navigazione generali | | conservative | Pointerdown/touchstart | 50-200ms | Pagine a minor confidenza o costose |

In pratica, moderate è il punto ottimale per la maggior parte dei siti. Dà 200-800ms di lead time (durata hover prima del click), sufficiente per pre-renderizzare completamente una pagina tipica.

Ho strumentato il comportamento hover su un sito di documentazione con 40.000 visitatori giornalieri:

document.querySelectorAll('a[href]').forEach(link => {
 let hoverStart = 0;

 link.addEventListener('pointerenter', () => {
 hoverStart = performance.now();
 });

 link.addEventListener('click', () => {
 const hoverDuration = performance.now() - hoverStart;
 navigator.sendBeacon('/analytics/hover', JSON.stringify({
 duration: Math.round(hoverDuration),
 url: link.href,
 clicked: true,
 }));
 });
});

Risultati da 2 settimane di dati:

  • Mediana hover-to-click: 380ms
  • 75esimo percentile: 620ms
  • 90esimo percentile: 1.100ms
  • Tasso hover-to-click (hover > 200ms): 68% degli hover risultavano in click

Con eagerness moderate (soglia 200ms), prediciamo correttamente il 68% delle navigazioni.

Speculation Rules Dinamiche

Le regole statiche in HTML funzionano per casi semplici, ma i siti reali hanno bisogno di regole dinamiche basate sul comportamento utente:

function updateSpeculationRules(urls) {
 document.querySelectorAll('script[type="speculationrules"]')
 .forEach(el => el.remove());

 const script = document.createElement('script');
 script.type = 'speculationrules';
 script.textContent = JSON.stringify({
 prerender: [{
 urls: urls.highConfidence,
 eagerness: 'moderate',
 }],
 prefetch: [{
 urls: urls.lowConfidence,
 eagerness: 'conservative',
 }],
 });
 document.head.appendChild(script);
}

function predictNextPage() {
 const scrollPercent = window.scrollY / (document.body.scrollHeight - window.innerHeight);

 if (scrollPercent > 0.7) {
 const nextLink = document.querySelector('a.pagination-next');
 if (nextLink) {
 updateSpeculationRules({
 highConfidence: [nextLink.href],
 lowConfidence: [],
 });
 }
 }
}

window.addEventListener('scroll', debounce(predictNextPage, 500), { passive: true });

Uso questo pattern su un blog dove gli articoli hanno link "prossimo post". Quando l'utente scrolla oltre il 70% dell'articolo, aggiungo una regola di prerender per il prossimo post. Quando arrivano in fondo e cliccano "Successivo," la pagina è già completamente renderizzata.

Gestire Analytics ed Effetti Collaterali

La più grande insidia in produzione col prerender sono gli effetti collaterali. Le pagine pre-renderizzate eseguono JavaScript. Se il tuo analytics spara page_view durante il prerender, conterai doppio.

Chrome fornisce la proprietà document.prerendering e l'evento prerenderingchange:

function trackPageView() {
 if (document.prerendering) {
 document.addEventListener('prerenderingchange', () => {
 sendPageView();
 }, { once: true });
 } else {
 sendPageView();
 }
}

function sendPageView() {
 const activationStart = performance.getEntriesByType('navigation')[0]?.activationStart || 0;

 gtag('event', 'page_view', {
 page_location: window.location.href,
 page_title: document.title,
 prerendered: activationStart > 0,
 activation_delay_ms: activationStart > 0
 ? Math.round(performance.now() - activationStart)
 : 0,
 });
}

trackPageView();

Core Web Vitals: L'Impatto Misurato

Ho eseguito un esperimento controllato su un sito contenuto con ~200.000 visitatori mensili. Due settimane senza speculation rules (controllo), due settimane con (esperimento). Dati CrUX:

Periodo Controllo (senza speculazione):

| Metrica | p75 | Good (%) | |---------|-----|----------| | LCP | 2.1s | 72% | | INP | 180ms | 85% | | CLS | 0.04 | 94% |

Periodo Esperimento (speculation rules, eagerness moderate):

| Metrica | p75 | Good (%) | |---------|-----|----------| | LCP | 0.8s | 94% | | INP | 95ms | 96% | | CLS | 0.02 | 97% |

LCP sceso da 2.1s a 0.8s al 75esimo percentile. Non è un errore di battitura. Quando una pagina è pre-renderizzata, la "navigazione" è essenzialmente swappare un tab nascosto in primo piano. L'elemento LCP è già dipinto.

INP migliorato da 180ms a 95ms. Questa mi ha sorpreso. Il miglioramento viene dal fatto che le pagine pre-renderizzate hanno già completato il parsing e l'esecuzione iniziale del JavaScript. Gli event handler sono già registrati. Il main thread è idle quando l'utente arriva.

Il costo di banda è stato misurabile ma ragionevole: ~15% di aumento nel trasferimento dati totale per sessione. In media, abbiamo pre-renderizzato 2.3 pagine per sessione, di cui 1.6 sono state effettivamente visitate (68% hit rate).

Ottimizzazione INP: La Connessione col Prerender

INP (Interaction to Next Paint) misura il tempo da un'interazione utente al prossimo aggiornamento visivo. Su una navigazione tradizionale, la prima interazione su una nuova pagina ha spesso INP elevato perché il browser sta ancora parsando JavaScript e il main thread è occupato.

Con prerender, tutto questo succede durante la fase nascosta di prerender. Quando l'utente interagisce con la pagina attivata, il main thread è idle e gli event handler sono registrati.

Risultati INP prima interazione:

  • Senza prerender: Mediana 142ms, p75 215ms, p95 380ms
  • Con prerender: Mediana 48ms, p75 82ms, p95 140ms

Il miglioramento p95 da 380ms a 140ms è significativo. Quei casi coda sono tipicamente causati da pagine con inizializzazione JavaScript pesante che blocca il main thread durante la finestra di prima interazione. Prerender elimina quell'intera classe di regressione INP.

Implementazione per Diversi Framework

Per Next.js, aggiungi speculation rules nel tuo layout:

// app/layout.js
export default function RootLayout({ children }) {
 return (
 <html>
 <head>
 <script
 type="speculationrules"
 dangerouslySetInnerHTML={{
 __html: JSON.stringify({
 prerender: [{
 where: {
 and: [
 { href_matches: "/*" },
 { not: { href_matches: "/api/*" } },
 { not: { href_matches: "/auth/*" } },
 ],
 },
 eagerness: "moderate",
 }],
 }),
 }}
 />
 </head>
 <body>{children}</body>
 </html>
 );
}

La Speculation Rules API è il più grande singolo miglioramento che ho fatto alla performance di navigazione percepita in anni. L'implementazione è diretta — un blob JSON nel tuo HTML. L'impatto è immediato e misurabile nei dati CrUX entro 28 giorni. Le insidie con analytics ed effetti collaterali sono gestibili con l'API document.prerendering.

Se ti importano i Core Web Vitals — e se stai leggendo questo, probabilmente sì — questo dovrebbe essere nella tua lista di implementazione questo trimestre. Il miglioramento LCP da solo vale lo sforzo. Il miglioramento INP è il bonus che lo rende trasformativo.

speculation-rulesprerenderprefetchcore-web-vitalsinpweb-performancechromenavigation

Strumenti menzionati in questo articolo

VercelProva Vercel
CloudflareProva Cloudflare
Divulgazione: Alcuni link in questo articolo sono link di affiliazione. Se ti registri tramite questi, potrei guadagnare una commissione senza costi aggiuntivi per te. Raccomando solo strumenti che uso e di cui mi fido personalmente.
Compartir
Seguime