En esta guía repasamos las tres métricas de Core Web Vitals tal como funcionan en 2026 (LCP, INP y CLS) y lo que hacemos en WordPress, clásico o headless, para mejorarlas: conexiones anticipadas, precarga y prioridad de la imagen principal, fuentes que no mueven el maquetado, CSS más ligero y JavaScript que no bloquea las interacciones. También te contamos cómo medir sin engañarte con los datos de laboratorio y repasamos los errores más habituales: la imagen principal con loading="lazy", font-display: block como supuesta cura del CLS, imágenes sin dimensiones o seguir optimizando para FID, una métrica que ya no existe.
Las tres métricas en 2026
Core Web Vitals son las métricas de experiencia de usuario que Google mide con datos reales de Chrome. Desde el 12 de marzo de 2024 son estas tres:
- LCP (Largest Contentful Paint): cuánto tarda en pintarse el elemento más grande visible al cargar (normalmente una imagen destacada o un bloque de texto). Bueno: ≤ 2,5 s.
- INP (Interaction to Next Paint): cuánto tarda la página en responder visualmente a las interacciones (clics, toques, teclado) durante toda la visita. Bueno: ≤ 200 ms; por encima de 500 ms es malo.
- CLS (Cumulative Layout Shift): cuánto se mueve el contenido de forma inesperada. Bueno: ≤ 0,1.
Los umbrales se evalúan en el percentil 75 de las visitas reales, separando móvil y escritorio. Dos métricas que aparecen en artículos antiguos (incluidos algunos nuestros) ya no cuentan: FID (First Input Delay), que INP sustituyó porque solo medía el retraso de la primera interacción, y FMP (First Meaningful Paint), que Lighthouse retiró hace años. FCP sigue existiendo como métrica de diagnóstico, pero no es una Core Web Vital.
Cómo medir: campo frente a laboratorio
Antes de tocar nada, conviene saber qué mide cada herramienta.
PageSpeed Insights: dos informes en una página
PageSpeed Insights muestra arriba los datos de campo de CrUX (Chrome User Experience Report): visitas reales de los últimos 28 días, por URL si hay tráfico suficiente o por origen si no lo hay. Esto es lo que usa Google. Debajo aparece el informe de Lighthouse, una prueba de laboratorio con un dispositivo y una red simulados. La puntuación de 0 a 100 es de laboratorio: sirve para diagnosticar, no es lo que evalúa Google.
- Si el sitio no tiene suficiente tráfico, no habrá datos de campo. En ese caso toca medir con tus propios usuarios (ver más abajo).
- El laboratorio no puede medir INP porque no hay nadie haciendo clic. Usa TBT (Total Blocking Time) como indicador aproximado: si el TBT es alto, es probable que el INP sufra.
- Los cambios tardan en reflejarse en campo: al ser una ventana de 28 días, una mejora desplegada hoy se verá poco a poco.
- El informe de Core Web Vitals de Search Console agrupa URLs similares con esos mismos datos: útil para detectar plantillas problemáticas.
Medir con usuarios reales: la librería web-vitals
La librería web-vitals de Google calcula las métricas igual que Chrome y te permite enviarlas a tu analítica o a un endpoint propio. Es la forma de tener datos de campo en sitios con poco tráfico y, sobre todo, de saber qué elemento es el LCP o qué interacción es lenta, gracias a la versión con atribución.
import { onCLS, onINP, onLCP } from 'web-vitals/attribution';
function enviar(metric) {
const body = JSON.stringify({
name: metric.name,
value: metric.value,
rating: metric.rating, // 'good' | 'needs-improvement' | 'poor'
id: metric.id,
attribution: metric.attribution,
url: location.pathname,
});
navigator.sendBeacon('/api/vitals', body);
}
onCLS(enviar);
onINP(enviar);
onLCP(enviar);
Las versiones recientes de la librería ya no incluyen onFID(); si tienes un código antiguo que lo importa, fallará al actualizar. Para depurar en local, el panel Performance de Chrome DevTools muestra las tres métricas en vivo y el elemento responsable de cada una.
LCP: que la imagen principal llegue primero
En la mayoría de páginas de WordPress el elemento LCP es la imagen destacada o la imagen del bloque hero. El tiempo de LCP se reparte entre la respuesta del servidor (TTFB), el retraso hasta que el navegador descubre la imagen, su descarga y el pintado. Atacamos cada tramo.
1. No hagas lazy load de la imagen LCP
WordPress añade loading="lazy" de forma nativa desde la 5.5, y desde la 6.3 intenta no aplicarlo a la primera imagen grande y marca la candidata a LCP con fetchpriority="high". Las heurísticas fallan a menudo con constructores, sliders o imágenes de fondo. Cuando la plantilla sabe cuál es la imagen principal, díselo explícitamente:
<?php
// En la plantilla del hero o de la entrada
echo wp_get_attachment_image(
get_post_thumbnail_id(),
'large',
false,
[
'loading' => 'eager',
'fetchpriority' => 'high',
'decoding' => 'async',
'sizes' => '(max-width: 768px) 100vw, 1200px',
]
);
Si usas un plugin de optimización que añade su propio lazy load con JavaScript, revisa que excluya la imagen principal: el lazy load con JS (el de los plugins basados en jQuery de hace unos años) retrasa todavía más el LCP, porque la imagen no empieza a descargarse hasta que se ejecuta el script. Hoy el atributo nativo es suficiente para el resto de imágenes.
2. Que el navegador la descubra pronto
Una imagen en el HTML inicial se descubre enseguida. Una imagen de fondo en CSS, o una que inserta JavaScript (sliders, componentes que se montan en el cliente), no se descubre hasta que se descarga y procesa ese recurso. Si la imagen LCP no puede estar en el HTML, precárgala. WordPress tiene desde la 6.1 el filtro wp_preload_resources, que acepta imagesrcset, imagesizes y, desde la 6.6, fetchpriority:
<?php
add_filter( 'wp_preload_resources', function ( $resources ) {
if ( ! is_front_page() ) {
return $resources;
}
$id = get_theme_mod( 'hero_image_id' ); // o el campo ACF del hero
if ( $id ) {
$resources[] = [
'href' => wp_get_attachment_image_url( $id, 'large' ),
'as' => 'image',
'imagesrcset' => wp_get_attachment_image_srcset( $id, 'large' ),
'imagesizes' => '100vw',
'fetchpriority' => 'high',
];
}
return $resources;
} );
Precarga solo el recurso LCP. Precargar muchas cosas hace que compitan entre sí por el ancho de banda y el resultado suele ser peor.
3. Preconnect al dominio que sirve los medios
Si las imágenes salen de otro dominio (un CDN de medios o, en headless, el propio WordPress cuando el front está en otro host), el navegador tiene que resolver DNS, abrir la conexión TCP y negociar TLS antes de pedir nada. preconnect adelanta ese trabajo. En vez de editar header.php a mano, usa el filtro wp_resource_hints:
<?php
add_filter( 'wp_resource_hints', function ( $urls, $relation_type ) {
if ( 'preconnect' === $relation_type ) {
$urls[] = 'https://media.example.com';
// Para fuentes hace falta crossorigin (se piden en modo CORS)
$urls[] = [
'href' => 'https://fonts.example.com',
'crossorigin' => 'anonymous',
];
}
return $urls;
}, 10, 2 );
Limítalo a uno o dos orígenes críticos para la primera vista. Si el recurso está en tu propio dominio, preconnect no aporta nada: la conexión ya está abierta.
4. Imágenes ligeras y servidor rápido
Una imagen LCP de varios cientos de KB tarda en bajar aunque tenga prioridad alta. Sirve tamaños adecuados con srcset y formatos modernos: WordPress soporta WebP desde la 5.8 y AVIF desde la 6.5. Lo detallamos en cómo optimizar imágenes en WordPress. Y no olvides el primer tramo: si el TTFB es alto, nada de lo anterior compensa. Caché de página, compresión (Brotli), HTTP/2 o HTTP/3 y una base de datos sana son la base.
5. En headless y Gatsby
Este blog es un WordPress headless con Gatsby desplegado en Netlify, y aquí el LCP depende sobre todo del front. Con gatsby-plugin-image, el componente GatsbyImage aplica lazy load por defecto; para la imagen del hero pásale loading="eager". Al ser HTML estático, el TTFB es bajo y la imagen ya está en el HTML inicial. Más contexto en Gatsby con WordPress.
INP: que el hilo principal esté libre
INP mide desde que el usuario interactúa hasta que el navegador pinta el siguiente fotograma. Se descompone en tres partes: el retraso de entrada (el hilo principal estaba ocupado con otra cosa), el tiempo de procesamiento de tus manejadores de eventos y el retraso de presentación (recalcular estilos, maquetar y pintar). En WordPress los culpables habituales son el JavaScript de plugins y terceros y los DOM enormes.
1. Carga menos JavaScript, y más tarde
- Usa
deferen los scripts que no son críticos. Desde WordPress 6.3,wp_enqueue_script()acepta la estrategia de carga en el argumento$args:array( 'strategy' => 'defer' ). - Revisa qué plugins cargan sus scripts en todas las páginas aunque solo se usen en una (formularios, sliders, galerías) y desencólalos donde no hacen falta.
- Controla los scripts de terceros (analítica, chats, anuncios, píxeles): suelen ser los que más tareas largas generan. Lo tratamos en cómo reducir el impacto de anuncios y scripts de seguimiento.
2. Trocea las tareas largas
Si un manejador de clic tiene que hacer mucho trabajo, actualiza primero la interfaz y cede el hilo principal antes de seguir. scheduler.yield() es la forma moderna de hacerlo; como no todos los navegadores lo soportan, conviene un respaldo con setTimeout:
function cederHilo() {
if (globalThis.scheduler?.yield) {
return scheduler.yield();
}
return new Promise((resolve) => setTimeout(resolve, 0));
}
boton.addEventListener('click', async () => {
boton.classList.add('is-loading'); // feedback visual inmediato
await cederHilo(); // deja pintar
filtrarResultados(); // trabajo pesado después
});
Para cálculos realmente pesados, sácalos del hilo principal con Web Workers.
3. Reduce el coste de pintar
Cuanto más grande es el DOM, más caro es cada recálculo de estilos tras una interacción. Los constructores visuales tienden a anidar contenedores sin necesidad; lo explicamos en reducir el tamaño excesivo del DOM. Para animaciones, usa transform y opacity en vez de propiedades que fuerzan un nuevo maquetado (más en animaciones con GSAP y Core Web Vitals).
CLS: que nada se mueva
Las dos causas más frecuentes de CLS en WordPress son imágenes, iframes o anuncios sin espacio reservado, y fuentes web que, al cargar, cambian el tamaño del texto.
1. Dimensiones en imágenes, vídeos e iframes
Con width y height en el HTML, el navegador calcula la proporción y reserva el hueco antes de descargar la imagen. WordPress los añade a las imágenes del contenido, pero revisa las que pinta tu tema a mano, los embeds y los SVG. En CSS, mantén la imagen fluida sin perder la proporción:
img,
video {
max-width: 100%;
height: auto;
}
/* Contenedores de embeds, banners o anuncios */
.embed-video {
aspect-ratio: 16 / 9;
}
.banner-slot {
min-height: 250px;
}
Lo mismo para lo que se inserta tras la carga (avisos de cookies, banners, resultados por AJAX): resérvale espacio o muéstralo superpuesto.
2. Fuentes: elige bien font-display
Un consejo que circuló mucho (y que nosotros también dimos) es usar font-display: block para evitar el CLS. No es buena idea: con block el texto es invisible hasta unos 3 segundos mientras llega la fuente, y cuando aparece puede seguir desplazando el maquetado si sus métricas son distintas a las de la fuente de respaldo. Las opciones razonables son:
font-display: optional: si la fuente no llega casi de inmediato, se usa la de respaldo durante esa visita y no hay intercambio. Cero CLS por fuentes, a cambio de que en la primera visita lenta no se vea la tipografía de marca.font-display: swapcon una fuente de respaldo ajustada: el texto se ve desde el principio y el cambio apenas mueve nada si las métricas coinciden.
3. Ajusta la fuente de respaldo con size-adjust
Los descriptores size-adjust, ascent-override, descent-override y line-gap-override permiten que una fuente local (Arial, por ejemplo) ocupe el mismo espacio que la fuente web. Los valores dependen de cada tipografía; herramientas como Fontaine o el ajuste automático de fuentes de Next.js los calculan por ti:
@font-face {
font-family: 'Roboto';
src: url('/fonts/roboto-latin-400.woff2') format('woff2');
font-weight: 400;
font-display: swap;
}
/* Respaldo con métricas ajustadas (valores de ejemplo) */
@font-face {
font-family: 'Roboto Fallback';
src: local('Arial');
size-adjust: 100.3%;
ascent-override: 92%;
descent-override: 24%;
line-gap-override: 0%;
}
body {
font-family: 'Roboto', 'Roboto Fallback', sans-serif;
}
4. Autoaloja y precarga solo lo necesario
Servir las fuentes desde tu propio dominio evita una conexión extra a un tercero y te permite precargar el archivo. Usa solo WOFF2, limita el número de pesos y estilos y, si puedes, sirve un subconjunto (por ejemplo, solo latín). Precarga únicamente la fuente del texto que aparece en la primera vista; el atributo crossorigin es obligatorio aunque sea tu dominio:
<link rel="preload" href="/fonts/roboto-latin-400.woff2"
as="font" type="font/woff2" crossorigin>
En WordPress, la Biblioteca de fuentes del editor (desde la 6.5) descarga las fuentes de Google a tu servidor al instalarlas. En Gatsby y otros frontends con npm, hace unos años recomendábamos los paquetes typeface-*; ese proyecto está obsoleto y su sucesor es Fontsource (@fontsource/open-sans), que se importa en gatsby-browser.js y empaqueta las fuentes con tu build.
CSS y HTML: la base que lo sostiene todo
CSS crítico y CSS sin usar
El CSS bloquea el renderizado: hasta que no se descarga y procesa, no se pinta nada, y eso retrasa FCP y LCP.
- Compila minificado. Con Dart Sass:
sass --style=compressed src/main.scss dist/main.css. - Elimina CSS sin usar. Temas multipropósito y frameworks cargan miles de reglas que la página no usa. Herramientas como PurgeCSS las eliminan en el build; en WordPress, ojo con las clases que se generan desde el editor o desde PHP, que hay que añadir a la lista de seguras.
- Inlinea el CSS crítico. Las reglas necesarias para la primera vista van en un
<style>en el<head>y el resto se carga después. En temas de bloques, WordPress ya carga los estilos de cada bloque solo cuando el bloque aparece en la página. - Divide por plantilla en vez de servir un único archivo gigante. Con HTTP/2 y HTTP/3 varias peticiones pequeñas ya no son el problema que eran; combinar todo en un solo CSS tiene menos sentido que hace unos años. Lo contamos en HTTP/2 y HTTP/3 en WordPress.
Si sigues usando @import en Sass para organizar los parciales, migra a @use y @forward: @import está obsoleto desde Dart Sass 1.80 y se eliminará en Dart Sass 3.0. El paquete sass-migrator hace la mayor parte del trabajo.
Un head ordenado
El HTML semántico no mejora las métricas por sí solo, pero ayuda a buscadores y lectores de pantalla, y un <head> ordenado evita bloqueos. Una base razonable:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Título de la página | Nombre del sitio</title>
<meta name="description" content="Resumen de la página en una o dos frases.">
<link rel="preconnect" href="https://media.example.com">
<link rel="preload" href="/fonts/roboto-latin-400.woff2" as="font" type="font/woff2" crossorigin>
<style>/* CSS crítico de la primera vista */</style>
<link rel="stylesheet" href="/css/main.css">
<script src="/js/main.js" defer></script>
</head>
<body>
<header><nav><!-- menú principal --></nav></header>
<main>
<h1>Título principal</h1>
<img src="/img/hero-1200.avif" width="1200" height="600"
fetchpriority="high" alt="Descripción de la imagen">
</main>
<footer><!-- pie --></footer>
</body>
</html>
Para la navegación entre páginas, WordPress 6.8 incluye en el núcleo las Speculation Rules, que precargan la siguiente página cuando el usuario muestra intención de visitarla; lo explicamos en prefetch de enlaces en WordPress.
Lista de comprobación
- Mira los datos de campo (CrUX) antes que la puntuación de Lighthouse, y separa móvil de escritorio.
- Identifica el elemento LCP de cada plantilla: sin
loading="lazy", confetchpriority="high"y presente en el HTML inicial o precargado. - Imágenes con
width,heightysrcset, en WebP o AVIF. - Fuentes autoalojadas en WOFF2, con
swapy respaldo ajustado uoptional; nuncablockcomo solución al CLS. - Scripts con
defer, sin plugins cargando JavaScript donde no se usa, y tareas largas troceadas. - Mide con
web-vitalsen producción para saber qué elemento o interacción falla.
Ninguno de estos cambios es mágico por separado. Lo que funciona es medir, atacar el tramo que más pesa y volver a medir con datos reales.