Un sitio puede cumplir los tres umbrales de Core Web Vitals y seguir sintiéndose lento al navegar. Esa fue la observación que ordenó el trabajo en este proyecto: una tienda WooCommerce del sector de decoración del hogar donde cada clic en una tarjeta de producto obligaba al usuario a esperar una página nueva completa. Arreglar LCP, CLS e INP resolvió la primera pantalla. Speculation Rules resolvió todo lo que venía después.
A continuación va el detalle técnico, empezando por la parte menos documentada en español (el prerender) y siguiendo por las métricas clásicas.
El punto de partida, medido antes de tocar nada
Todas las cifras siguientes son de laboratorio y de campo sobre el mismo sitio, y cualquier lector puede reproducir el método con Lighthouse y con el informe de Core Web Vitals de Search Console.
| Métrica | Antes | Después |
|---|---|---|
| Puntuación móvil en Lighthouse | 42 | 100 |
| LCP (móvil, percentil 75) | 4,8s | 1,2s |
| INP | 450ms | 48ms |
| CLS | 0,25 | 0,00 |
| TTFB desde España | 600ms | 40ms |
El orden de trabajo no fue el orden de la tabla. Primero se toca el servidor, porque el TTFB es un suelo: ningún truco de front-end recupera medio segundo perdido antes del primer byte.
1. Speculation rules: prerender sin romper la analítica ni el carrito
La API de Speculation Rules sustituye a las viejas heurísticas de <link rel="prefetch"> y a los plugins que hacían prefetch con JavaScript al pasar el cursor. La diferencia no es de rendimiento, es de naturaleza: prefetch descarga el documento, prerender construye la página entera en un proceso oculto, con su CSS, su JavaScript y sus imágenes ya decodificadas. Cuando el usuario hace clic, el navegador activa ese proceso en lugar de navegar.
Se declara con un bloque JSON en el propio documento:
<script type="speculationrules">
{
"prerender": [{
"where": { "href_matches": "/producto/*" },
"eagerness": "moderate"
}],
"prefetch": [{
"where": { "href_matches": "/categoria/*" },
"eagerness": "conservative"
}]
}
</script>eagerness es el parámetro que decide el coste. Con immediate el navegador especula en cuanto lee la regla, con eager en cuanto el enlace entra en juego, con moderate cuando el cursor se queda sobre el enlace unos cientos de milisegundos, y con conservative solo al empezar el clic. En una tienda con cincuenta productos por categoría, immediate significa cincuenta páginas renderizadas que nadie pidió, con su coste de CPU en el móvil del usuario y su coste de ancho de banda en el origen. Usamos moderate para fichas de producto y conservative para el resto.
Lo que se rompe cuando la página existe antes de que el usuario llegue
Una página prerenderizada ejecuta su JavaScript en un documento que todavía no es visible. Tres cosas fallan de forma silenciosa si no se tratan.
La analítica cuenta visitas fantasma. Google Analytics 4 y Umami respetan la API de Page Visibility y retrasan el evento de vista hasta la activación, pero cualquier script propio que dispare una llamada en DOMContentLoaded va a registrar sesiones que nunca ocurrieron. La comprobación es explícita:
if (document.prerendering) {
document.addEventListener('prerenderingchange', enviarVistaDePagina, { once: true });
} else {
enviarVistaDePagina();
}El estado del carrito se congela. WooCommerce sirve los fragmentos del carrito por AJAX; si la página se prerenderiza antes de que el usuario añada un producto y se activa después, el mini carrito muestra el estado antiguo. La solución no es desactivar el prerender, es refrescar los fragmentos en el evento prerenderingchange y excluir del prerender todo lo transaccional: /carrito/, /finalizar-compra/, /mi-cuenta/ y cualquier URL con parámetros de un solo uso. Las peticiones que no son GET nunca se especulan, eso lo garantiza el navegador, pero un enlace GET que borra una línea del carrito sí se ejecutaría, y ese patrón sigue existiendo en muchos temas antiguos.
Las pruebas A/B y los banners de consentimiento se ven a sí mismos dos veces. Cualquier script que escriba una cookie al cargar la va a escribir en el documento especulado y otra vez en el activado.
Los límites que el navegador impone y que conviene conocer
Speculation Rules solo está implementada en navegadores basados en Chromium. En Safari y en Firefox el bloque JSON se ignora, así que el sitio debe seguir siendo rápido sin él: el prerender es una capa encima de un sitio ya optimizado, nunca el sustituto de esa optimización. El navegador además impone su propio presupuesto (un número limitado de páginas especuladas a la vez, menos con moderate que con immediate), cancela especulaciones cuando hay poca memoria, y respeta el modo de ahorro de datos. En Chrome, chrome://prerender-internals muestra cada especulación y el motivo exacto por el que se descartó, que es la única forma fiable de depurar esto: desde la pestaña de red no se ve nada.
El compromiso es honesto: se cambia tráfico y CPU por latencia percibida. En un catálogo con muchas visitas de rebote, gran parte de ese trabajo se tira. Por eso moderate sobre un conjunto acotado de URLs es casi siempre mejor decisión que especular todo el menú.
2. CLS: el cero no se consigue, se defiende
El CLS fue la métrica que más tiempo consumió, y no por dificultad técnica sino porque se rompe desde sitios que no aparecen en Lighthouse. La puntuación de laboratorio mide unos segundos con la página quieta. El CLS de campo mide ventanas de sesión durante toda la vida del documento, agrupando desplazamientos que ocurren con menos de un segundo de separación en ventanas de cinco segundos como máximo, y se queda con la peor. Un banner que entra al sexto segundo no aparece en Lighthouse y sí en Search Console.
Fuentes: el salto no está en la carga, está en la diferencia de métricas
El villano original eran las fuentes personalizadas: el texto se pintaba con la fuente del sistema y saltaba al llegar la fuente web. font-display: optional corta el problema de raíz porque el navegador se queda con la fuente de respaldo si la web no llega en su ventana inicial, y ya no cambia en esa carga.
Eso convierte el problema de rendimiento en un problema tipográfico: si la fuente de respaldo tiene otra altura de x y otro ancho medio, el bloque de texto ocupa otro número de líneas. La corrección se hace declarando una familia de respaldo ajustada:
@font-face {
font-family: 'Inter fallback';
src: local('Arial');
size-adjust: 107%;
ascent-override: 90%;
descent-override: 22%;
line-gap-override: 0%;
}Con size-adjust, ascent-override y descent-override la fuente de respaldo ocupa exactamente la misma caja que la definitiva, así que el intercambio deja de mover nada. Es trabajo manual, se calibra comparando capturas del mismo párrafo con y sin la fuente web, y es la diferencia entre un CLS que baja y un CLS que llega a cero.
Además, la fuente se precarga con <link rel="preload" as="font" type="font/woff2" crossorigin>. El atributo crossorigin no es opcional aunque la fuente sea del mismo origen: sin él el navegador descarga el archivo dos veces.
Reserva de espacio: dónde llega aspect-ratio y dónde no
Todas las imágenes llevan width y height en el HTML, que el navegador traduce a una relación de aspecto implícita, y los contenedores llevan aspect-ratio en CSS para los casos responsivos donde la proporción cambia por punto de ruptura. Eso cubre el catálogo entero.
Lo que no cubre es el resto del inventario de desplazamientos, que en esta tienda incluía cuatro casos: el aviso de envío gratuito que se inyectaba tras leer la geolocalización, el selector de tallas que aparecía solo en productos con variaciones, las insignias de valoraciones cargadas desde un servicio externo y el propio banner de cookies. Para los cuatro la solución es la misma y es aburrida: reservar la altura máxima posible en el servidor, con un contenedor vacío, y dejar que el contenido entre dentro de ese hueco. Se paga con un poco de espacio en blanco durante unos milisegundos. Es un precio bajo comparado con un botón que se mueve justo cuando el pulgar baja.
El detalle que casi se escapa: bfcache y navegación hacia atrás
Cuando el usuario vuelve atrás y el navegador restaura la página desde bfcache, los scripts que reconstruyen componentes al restaurar pueden provocar un desplazamiento que cuenta para el CLS de la sesión. Se comprueba en el panel Application de DevTools, en la sección Back forward cache, y se corrige escuchando pageshow con event.persisted en lugar de rehacer el trabajo entero.
3. LCP: quitar dependencias pesa más que optimizar bytes
El elemento LCP era la imagen del hero, servida por un slider comercial que cargaba varios megabytes de JavaScript antes de pintar el primer píxel. Ninguna optimización de imagen arregla eso, porque el problema no es el peso sino la cadena: el navegador necesitaba analizar el HTML, descargar el script, ejecutarlo y solo entonces descubrir qué imagen tocaba pedir.
El slider se sustituyó por una maquetación estática con CSS Grid, de forma que la imagen aparece en el HTML inicial y el analizador de precarga del navegador la encuentra sin ejecutar nada. Sobre esa base, dos ajustes:
fetchpriority="high"en la imagen del hero, que le dice al navegador que la adelante al logotipo y a los iconos del menú.- AVIF con respaldo en WebP mediante
<picture>. La imagen de cabecera pasó de 800KB en PNG a 45KB. AVIF codifica más despacio en el servidor que WebP, así que la conversión se hace una vez al subir el archivo, no en cada petición.
La carga diferida se retiró de todo lo que está por encima del pliegue. Un loading="lazy" en la imagen del hero es una de las formas más rápidas de empeorar el LCP sin darse cuenta.
4. INP: sacar trabajo del hilo principal tiene un precio
Los culpables eran los scripts de terceros: chat, píxel publicitario, gestor de etiquetas y mapas de calor compitiendo por el hilo principal. Con el hilo ocupado midiendo al usuario, abrir el menú tardaba más que cargar la página.
Partytown mueve esos scripts a un Web Worker. La parte que casi nunca se cuenta es lo que cuesta: el worker no tiene DOM, así que Partytown intercepta cada acceso mediante proxies y lo reenvía al hilo principal de forma síncrona con SharedArrayBuffer o con peticiones bloqueantes al service worker. Los scripts que leen el DOM en bucle se vuelven más lentos dentro del worker, y SharedArrayBuffer exige cabeceras COOP y COEP que rompen cualquier iframe de terceros que no envíe CORP. En este proyecto eso obligó a dejar el widget de chat fuera de Partytown y cargarlo bajo interacción del usuario.
En el lado de WooCommerce, el botón de añadir al carrito se refactorizó para que la interfaz responda de inmediato y la sincronización con el servidor ocurra después, con reversión visible si el servidor rechaza la operación. El objetivo de INP no es que el servidor sea rápido, es que el siguiente fotograma se pinte.
5. La infraestructura: el suelo sobre el que se construye todo lo demás
El TTFB entra entero en el presupuesto del LCP. Si el servidor tarda 600ms en empezar a responder, quedan menos de dos segundos para todo lo demás, descarga y decodificación de imagen incluidas.
Redis como caché de objetos elimina las consultas repetidas de menús, opciones y taxonomías, que en WooCommerce se cuentan por decenas en cada petición. La caché de HTML en el borde va un paso más allá y sirve el documento desde el punto de presencia más cercano al visitante, en lugar de generarlo en el origen.
El matiz que decide si esto funciona en una tienda es qué se hace con el carrito. Una caché de HTML ingenua sirve a un usuario el carrito de otro. La regla que usamos es servir desde el borde solo a sesiones sin cookie de carrito ni de sesión de WooCommerce, y saltar la caché en cuanto aparece cualquiera de ellas. Las páginas de producto y de categoría, que son la mayoría del tráfico de entrada, se benefician igual.
6. Por qué esto importa para el negocio
Los Core Web Vitals no son una nota de un examen. Cada una de las tres métricas describe un momento concreto en el que el usuario decide si sigue o se va: LCP es la espera antes de ver algo, CLS es la sensación de que la página no es de fiar, e INP es la frustración de tocar y que no pase nada.
No publicamos aquí cifras de conversión ni de tráfico atribuidas a este trabajo, y la razón es metodológica: en el mismo trimestre cambiaron el catálogo, la inversión publicitaria y la estacionalidad del sector, así que cualquier porcentaje que escribiéramos sería una correlación disfrazada de causa. Lo que sí es verificable es el mecanismo. La experiencia de la página es una señal declarada dentro del sistema de clasificación de Google y dentro del nivel de calidad de Google Ads, y un catálogo que se navega sin esperas elimina un motivo concreto de abandono.
Quien quiera medir el efecto en su propio sitio tiene el camino marcado: guardar el informe de Core Web Vitals de Search Console antes de empezar, anotar la fecha del despliegue y volver a mirar los mismos segmentos veintiocho días después. El instrumento primero, las conclusiones después.
Conclusión: la velocidad no es deuda técnica pendiente, es una decisión de arquitectura que se toma una vez y se defiende en cada despliegue posterior.
¿Su tienda WooCommerce pierde ventas por lentitud? En WPPoland optimizamos con el método de arriba, medición incluida.







