<!-- Canonical: https://lucioduran.com/es/blog/speculation-rules-api-prerender-navigation
Language: es
Content-SHA256: 8e2d279a2dbd2418cd67e4259bac862ce04aeaece6a77abbdc7f511388d363c1
-->

Web Performance

# Speculation Rules API: Pre-renderizado de Páginas Antes de la Navegación e Impacto Medido en Core Web Vitals

2026-03-057 min lectura

Lucio Durán
Engineering Manager & AI Solutions Architect

También disponible en: [English](<https://lucioduran.com/blog/speculation-rules-api-prerender-navigation>)

## La API: Cómo Funciona

Las speculation rules se declaran como un objeto JSON dentro de un tag ` <script type="speculationrules"> `:

```html
<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>
```

Esto le dice a Chrome: "Para cualquier link interno que no sea la página de logout o una ruta de API, pre-renderizala cuando el usuario muestre interés moderado (hover por 200ms). Para links del blog, prefetchealos con intención conservadora (pointerdown, el momento del click)."

La cláusula ` where ` soporta pattern matching de URLs (` href_matches `) y matching de selectores CSS (` selector_matches `). Puede componer reglas con ` and `, ` or `, y ` not `. Esto es vastamente más poderoso que el viejo ` <link rel=prerender> ` que solo podía apuntar a una sola URL.

## Niveles de Eagerness: El Espectro de Confianza

Hay cuatro niveles de eagerness, cada uno mapeando a una señal diferente de intención del usuario:

| Eagerness | Trigger | Lead Time Típico | Caso de Uso |
| --- | --- | --- | --- |
| ` immediate ` | Carga de página | Segundos | Navegación siguiente conocida (paso 2 de wizard) |
| ` eager ` | Link se hace visible | Segundos | Navegación de alta probabilidad |
| ` moderate ` | Hover (200ms) | 200-800ms | Links de navegación general |
| ` conservative ` | Pointerdown/touchstart | 50-200ms | Páginas de menor confianza o costosas |

En la práctica, ` moderate ` es el punto óptimo para la mayoría de los sitios. Te da 200-800ms de lead time (duración del hover antes del click), que es suficiente para pre-renderizar completamente una página típica.

Instrumenté el comportamiento de hover en un sitio de documentación con 40.000 visitantes diarios:

```javascript
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,
 }));
 });
});
```

Resultados de 2 semanas de datos:

- Mediana de hover-a-click: 380ms
- Percentil 75: 620ms
- Percentil 90: 1.100ms
- Tasa hover-a-click (hover \> 200ms): 68% de los hovers resultaron en click

Con eagerness ` moderate ` (umbral de 200ms), predecimos correctamente el 68% de las navegaciones. El 32% de prerenders desperdiciados es un costo de ancho de banda, pero Chrome maneja el lifecycle del prerender agresivamente, si la memoria está limitada, desaloja páginas pre-renderizadas.

## Speculation Rules Dinámicas

Las reglas estáticas en HTML funcionan para case es simples, pero los sitios reales necesitan reglas dinámicas basadas en comportamiento del usuario, A/B tests, o personalización:

```javascript
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);
}

// Predecir próxima página basado en profundidad de scroll
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 este patrón en un blog donde los artículos tienen links de "próximo post". Cuando el usuario scrollea más del 70% del artículo, agrego una regla de prerender para el próximo post. Para cuando llegan abajo y hacen click en "Siguiente," la página ya está completamente renderizada.

## Manejando Analytics y Efectos Secundarios

La trampa más grande en producción con prerender son los efectos secundarios. Las páginas pre-renderizadas ejecutan JavaScript. Si tu analytics dispara ` page_view ` durante el prerender, se va a contar doble.

Chrome provee la propiedad ` document.prerendering ` y el evento ` prerenderingchange `:

```javascript
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: El Impacto Medido

Corrí un experimento controlado en un sitio de contenido con ~200.000 visitantes mensuales. Dos semanas sin speculation rules (control), dos semanas con (experimento). Datos CrUX:

### Período Control (sin especulación):

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

### Período Experimento (speculation rules, eagerness moderate):

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

LCP bajó de 2.1s a 0.8s en el percentil 75. No es un error de tipeo. Cuando una página está pre-renderizada, la "navegación" es esencialmente swapear un tab oculto al frente. El elemento LCP ya está pintado.

INP mejoró de 180ms a 95ms. Esta me sorprendió. La mejora viene del hecho de que las páginas pre-renderizadas ya completaron el parsing y ejecución inicial de JavaScript. Los event handlers ya están registrados. El main thread está idle cuando el usuario llega.

El costo de ancho de banda fue medible pero razonable: ~15% de aumento en transferencia total de datos por sesión. En promedio, pre-renderizamos 2.3 páginas por sesión, de las cuales 1.6 fueron efectivamente visitadas (68% hit rate).

## Optimización de INP: La Conexión con Prerender

INP (Interaction to Next Paint) mide el tiempo desde una interacción del usuario hasta el siguiente update visual. En una navegación tradicional, la primera interacción en una nueva página frecuentemente tiene INP elevado porque el browser todavía está parseando JavaScript y el main thread está ocupado.

Con prerender, todo eso pasa durante la fase oculta de prerender. Para cuando el usuario interactúa con la página activada, el main thread está idle y los event handlers están registrados.

Resultados de INP de primera interacción:

- **Sin prerender**: Mediana 142ms, p75 215ms, p95 380ms
- **Con prerender**: Mediana 48ms, p75 82ms, p95 140ms

La mejora del p95 de 380ms a 140ms es significativa. Ese es case es de cola son típicamente causados por páginas con inicialización pesada de JavaScript que bloquea el main thread durante la ventana de primera interacción. Prerender elimina esa clase entera de regresión de INP.

## Implementación para Diferentes Frameworks

Para Next.js, agregar speculation rules en tu layout:

```jsx
// 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 es la mayor mejora individual que hice al rendimiento de navegación percibido en años. La implementación es directa, un blob JSON en tu HTML. El impacto es inmediato y medible en datos CrUX dentro de 28 días. Las trampas con analytics y efectos secundarios son manejables con la API ` document.prerendering `.

Si te importan los Core Web Vitals, y si se está leyendo esto, probablemente sí, esto debería estar en tu lista de implementación este trimestre. La mejora de LCP sola vale el esfuerzo. La mejora de INP es el bonus que lo hace transformador.

## Preguntas frecuentes

### La Speculation Rules API funciona en todos los browsers?

No. A principios de 2026, el soporte completo de prerender solo está en browsers basados en Chromium (Chrome, Edge, Opera). Safari y Firefox soportan la parte de prefetch pero no el prerender completo. La API está diseñada como progressive enhancement, los browsers que no la soportan simplemente ignoran las speculation rules, y la navegación funciona normalmente.

### Cómo difiere el prerender del viejo link rel=prerender?

El viejo link rel=prerender fue deprecado porque era un hint binario sin granularidad, el browser pre-renderizaba completamente una URL o nada. La Speculation Rules API agrega document rules (pattern matching), niveles de eagerness, scoring de confianza, y la capacidad de especificar múltiples URLs con diferentes estrategias. También funciona correctamente con la gestión de recurse es del browser, sin consumir memoria excesiva.

### El prerender puede causar problemas con analytics o tracking de ads?

Sí, esta es una preocupación real. Las páginas pre-renderizadas ejecutan JavaScript, lo que significa que los scripts de analytics van a disparar durante el prerender. Chrome posterga ciertas APIs (como triggers de Intersection Observer para viewability de ads) hasta que la página se activa realmente, pero no todos los proveedores de analytics manejan esto correctamente. Utilizar document.prerendering y el evento prerenderingchange para gatear llamadas de analytics.

## Tips

1. **LuchoTip:** Comenzar con reglas de ` prefetch ` antes de saltar a ` prerender `. Prefetch solo descarga el documento (típicamente 5-50KB), mientras que prerender descarga y ejecuta todo, stylesheets, scripts, imágenes. En una página con 2MB de assets, un prerender errado cuesta ancho de banda real. Utilizar ` prefetch ` para confianza moderada y ` prerender ` solo para navegaciones de alta confianza.
2. **LuchoTip:** Siempre colocar ` eagerness: "moderate" ` en tus speculation rules en producción en vez de ` "eager" `. Eager dispara inmediatamente al cargar la página para todas las URLs que matchean, lo que puede triggear docenas de prerenders simultáneos en páginas con muchos links. Moderate espera intención de hover (umbral de 200ms), que filtra hovers accidentales y ahorra 80% de especulaciones desperdiciadas.
3. **LuchoTip:** Probar las speculation rules con Chrome DevTools → Application → Speculative loads tab. Muestra qué URLs se están pre-renderizando, su estado, y razones de falla. Encontré reglas de especulación rotas que fallaban silenciosamente porque la página target tenía ` Cache-Control: no-store `, lo que bloquea el prerender completamente.

- speculation-rules
- prerender
- prefetch
- core-web-vitals
- inp
- web-performance
- chrome
- navigation

[Volver al Blog](<https://lucioduran.com/es/blog>)
