Qué son Core Web Vitals (LCP, INP, CLS) y por qué dependen del desarrollo
Core Web Vitals son métricas centradas en la experiencia real del usuario. En SEO técnico importan porque reflejan si una página carga rápido, responde con fluidez y mantiene estabilidad visual. Las tres métricas clave son:
- LCP (Largest Contentful Paint): tiempo hasta que se renderiza el elemento de contenido más grande visible en el viewport (típicamente una imagen hero, un bloque de texto grande o un banner). Objetivo habitual:
≤ 2.5s(p75). - INP (Interaction to Next Paint): latencia de interacción; mide cuánto tarda la UI en responder visualmente tras una interacción (click, tap, teclado). Objetivo habitual:
≤ 200ms(p75). - CLS (Cumulative Layout Shift): suma de cambios de layout inesperados durante la vida de la página. Objetivo habitual:
≤ 0.1(p75).
Estas métricas se ven afectadas por decisiones de desarrollo como: TTFB (tiempo hasta el primer byte), recursos render-blocking (CSS/JS que bloquean el render), hidratación y coste de JS, carga de fuentes, optimización de imágenes y dependencia de terceros (tags, widgets, A/B testing, chat, analytics).
Medición: lab vs field (cómo medir sin engañarte)
Field data (datos reales)
Los datos de campo reflejan usuarios reales (dispositivos, redes, CPU, cachés, rutas). Para SEO técnico, el criterio típico es el percentil 75 (p75) por URL o grupo de URLs. Úsalos para decidir prioridades y validar impacto real.
- Ventajas: representa la realidad; detecta problemas intermitentes (terceros, picos de CPU, redes lentas).
- Limitaciones: tarda en acumularse; es menos útil para depurar una causa exacta.
Instrumentación recomendada: RUM (Real User Monitoring) propio con Web Vitals o un proveedor. Ejemplo mínimo con la librería web-vitals para enviar métricas a tu endpoint:
import {onLCP, onINP, onCLS} from 'web-vitals';
function sendToAnalytics(metric) {
navigator.sendBeacon('/rum', JSON.stringify({
name: metric.name,
value: metric.value,
id: metric.id,
rating: metric.rating,
url: location.href
}));
}
onLCP(sendToAnalytics);
onINP(sendToAnalytics);
onCLS(sendToAnalytics);Lab data (datos sintéticos)
Los datos de laboratorio se obtienen en un entorno controlado (misma red/CPU, mismo estado de caché). Úsalos para iterar rápido y diagnosticar causas.
- Escuche el audio con la pantalla apagada.
- Obtenga un certificado al finalizar.
- ¡Más de 5000 cursos para que explores!
Descargar la aplicación
- Ventajas: reproducible; permite perfilar CPU, waterfall de red, cobertura de JS/CSS.
- Limitaciones: puede no reflejar rutas reales, personalización, variabilidad de terceros.
Herramientas típicas: Lighthouse/Chrome DevTools (Performance, Network, Coverage), WebPageTest, pruebas automatizadas en CI con perfiles de red/CPU.
Checklist de medición (paso a paso)
- Define el scope: plantillas críticas (home, categoría, ficha, artículo, checkout) y dispositivos objetivo (móvil primero).
- Recoge field: p75 de LCP/INP/CLS por plantilla y por país/dispositivo si aplica.
- Reproduce en lab: elige 3–5 URLs representativas por plantilla; ejecuta 3–5 corridas y toma medianas.
- Correlaciona: si field es malo pero lab es bueno, sospecha de terceros, rutas específicas, long tasks por hidratación, o variabilidad de red.
- Establece baseline: guarda resultados por commit/release para comparar.
Diagnóstico: qué dispara cada métrica y cómo encontrar el culpable
LCP: causas típicas y cómo detectarlas
LCP suele empeorar por una combinación de: TTFB alto, recurso LCP pesado (imagen grande sin optimizar), CSS/JS bloqueantes y priorización incorrecta (el navegador no descarga pronto el recurso que define el LCP).
| Disparador | Cómo se manifiesta | Cómo confirmarlo |
|---|---|---|
| TTFB alto (backend, caché, CDN) | La carga empieza tarde; todo se desplaza | DevTools Network: Waiting (TTFB) alto; logs de servidor; trazas |
| Render-blocking CSS | HTML llega, pero el render se retrasa | Waterfall: CSS en el critical path; Performance: tiempo hasta First Paint alto |
| JS bloqueante / hidratación pesada | El main thread está ocupado antes de pintar | Performance: long tasks; scripting alto antes del LCP |
| Imagen LCP sin optimizar | LCP es una imagen que tarda en descargarse/decodificar | Network: tamaño, prioridad, decode; Performance: Largest Contentful Paint apunta a la imagen |
| Fuentes web bloqueantes | Texto LCP tarda por FOIT/FOUT o reflow | Waterfall de fuentes; CSS @font-face; Layout shifts asociados |
| Terceros en el head | Bloquean o compiten por ancho de banda | Network: requests a dominios externos temprano; CPU por scripts |
Guía práctica para diagnosticar LCP (paso a paso):
- En DevTools > Performance, graba una carga en modo incógnito con caché desactivada.
- Identifica el evento
LCPy el elemento asociado (imagen o bloque de texto). - En Network, filtra por ese recurso (si es imagen/fuente) y revisa: tamaño, prioridad, si está en el primer chunk de HTML, y si hay redirecciones.
- Revisa el critical path: CSS bloqueante, scripts en
<head>, y long tasks antes del LCP. - Si el LCP depende de datos (renderizado tras fetch), evalúa SSR/streaming o placeholders que permitan pintar antes.
INP: causas típicas y cómo detectarlas
INP empeora cuando el hilo principal está ocupado y no puede procesar eventos de entrada ni pintar. Los culpables frecuentes son: exceso de JS, hidratación costosa (SPA/SSR hidratado), listeners globales, renderizados caros, y terceros que generan long tasks.
| Disparador | Ejemplo | Cómo confirmarlo |
|---|---|---|
| Long tasks (> 50ms) | Parse/compile de bundles grandes | Performance: bloques largos de scripting; Main thread saturado |
| Hidratación masiva | SSR con hidratación completa al cargar | Performance: scripting alto tras load; interacciones tempranas lentas |
| Re-render costoso | Listas grandes sin virtualización | React/Vue devtools + Performance; recálculo de estilo/layout |
| Handlers pesados | Click dispara lógica sin dividir | Performance: evento de input seguido de scripting y paint tardío |
| Terceros | Tag manager ejecuta scripts en interacción | Network/Performance: tareas atribuibles a scripts externos |
Guía práctica para diagnosticar INP (paso a paso):
- Abre DevTools > Performance y activa “Web Vitals” si está disponible.
- Graba y realiza interacciones representativas (abrir menú, filtrar, añadir al carrito).
- Localiza el evento de interacción con mayor latencia y revisa la cadena: input delay, processing time, presentation delay.
- Identifica qué funciones consumen CPU (call tree) y qué scripts aportan long tasks (incluidos terceros).
- Verifica si la interacción dispara reflow/layout masivo (Layout/Rendering en el timeline).
CLS: causas típicas y cómo detectarlas
CLS empeora cuando el contenido “salta” sin que el usuario lo espere. Causas comunes: imágenes/iframes sin dimensiones, inserciones tardías (banners, consent, ads), fuentes que cambian métricas, y componentes que reservan espacio después.
| Disparador | Ejemplo | Cómo confirmarlo |
|---|---|---|
Sin width/height o aspect-ratio | Imagen hero empuja el texto al cargar | DevTools: Layout Shift Regions; auditorías de CLS |
| Ads/iframes dinámicos | Slot publicitario colapsado que luego se expande | Shift ocurre tras requests a terceros |
| Consent banner | Inserción arriba del contenido | Shift al aparecer el banner; revisa DOM mutations |
| Fuentes web | FOIT/FOUT cambia el tamaño del texto | Shift coincide con carga de fuentes; waterfall de fonts |
| Contenido inyectado | Recomendaciones “related” insertadas | Shift tras fetch; mutation observer en RUM |
Guía práctica para diagnosticar CLS (paso a paso):
- En DevTools > Performance, graba una carga completa y observa “Layout Shifts”.
- Haz click en cada shift para ver los nodos afectados y las regiones resaltadas.
- Identifica si el shift proviene de: imágenes sin tamaño, iframes, fuentes o inserciones tardías.
- Repite con y sin terceros (bloqueando dominios en DevTools) para aislar su impacto.
Optimizaciones priorizadas (de mayor impacto a menor fricción)
1) Asegura un TTFB bajo (base para LCP)
- Caching: cachea HTML cuando sea posible (edge/SSR cache), y usa
Cache-Controlcorrecto para assets versionados. - CDN: sirve estáticos desde CDN; acerca el contenido al usuario.
- Optimiza backend: reduce consultas, usa compresión, evita render bloqueado por llamadas externas.
Ejemplo de cabeceras típicas para assets versionados:
Cache-Control: public, max-age=31536000, immutable2) Reduce render-blocking: critical CSS y carga diferida
- Critical CSS: inline del CSS mínimo para el above-the-fold; difiere el resto.
- Evita CSS enorme: divide por ruta/plantilla; elimina CSS no usado.
- Scripts: usa
deferpara JS propio no crítico;asyncpara scripts independientes (con cuidado del orden).
Patrón de carga diferida de CSS (ejemplo):
<link rel="preload" href="/styles/non-critical.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/styles/non-critical.css"></noscript>Patrón recomendado para scripts:
<script src="/app.js" defer></script>
<script src="https://third-party.example/tag.js" async></script>3) Optimiza el recurso LCP (normalmente imagen)
- Formato: AVIF/WebP cuando sea viable.
- Tamaño correcto: usa
srcset/sizespara servir la imagen adecuada por viewport. - Prioridad: marca la imagen LCP con alta prioridad y evita lazy-load en el hero.
- Preload: si el LCP es una imagen crítica, usa
rel="preload"para adelantar su descarga.
Ejemplo de imagen hero optimizada:
<link rel="preload" as="image" href="/img/hero-1280.avif" imagesrcset="/img/hero-640.avif 640w, /img/hero-1280.avif 1280w" imagesizes="100vw">
<img src="/img/hero-1280.avif"
srcset="/img/hero-640.avif 640w, /img/hero-1280.avif 1280w"
sizes="100vw"
width="1280" height="720"
fetchpriority="high"
decoding="async"
alt="">4) Reduce JS para mejorar INP (menos CPU, menos long tasks)
- Elimina dependencias: audita bundles y quita librerías redundantes.
- Code splitting: carga por ruta y por interacción (lazy import).
- Tree-shaking: asegúrate de compilar en modo producción y con módulos ES.
- Divide long tasks: usa
requestIdleCallbacko trocea trabajo; evita trabajo pesado en handlers. - Hidratación parcial: hidrata solo componentes interactivos (islands/partial hydration) o retrasa hidratación no crítica.
Ejemplo de carga bajo demanda por interacción:
button.addEventListener('click', async () => {
const {openFilters} = await import('./filters.js');
openFilters();
});5) Controla fuentes para evitar LCP lento y CLS
- Preconnect al origen de fuentes si es externo.
- Preload de la fuente crítica (solo si realmente es crítica).
- font-display: usa
swapooptionalpara reducir bloqueos. - Metric overrides: usa
size-adjust,ascent-override, etc., para minimizar cambios de layout.
Ejemplo de preconnect y font-display:
<link rel="preconnect" href="https://fonts.example.com" crossorigin>
@font-face {
font-family: 'Inter';
src: url('/fonts/inter.woff2') format('woff2');
font-display: swap;
}6) Evita CLS: reserva espacio y evita inserciones inesperadas
- Dimensiones: define
width/heightoaspect-ratioen imágenes, videos e iframes. - Slots estables: reserva espacio para ads/banners; evita empujar contenido.
- UI overlays: preferir overlays (sin reflow) para consent o promos, o insertar en zonas que no desplacen contenido.
Ejemplo con aspect-ratio:
.hero-media { aspect-ratio: 16 / 9; width: 100%; }
.hero-media img { width: 100%; height: 100%; object-fit: cover; }7) Gestiona terceros (impacto transversal en LCP e INP)
- Inventario: lista todos los scripts/iframes externos por plantilla y su propósito.
- Presupuesto por terceros: limita KB y tiempo de CPU permitido.
- Carga condicional: dispara scripts solo donde aportan valor (por ruta, por consentimiento, por interacción).
- Ubicación: evita terceros en el head si no son críticos; usa
defer/asyncy carga post-interacción cuando sea posible.
Preconnect, preload y prioridades: cuándo usarlos
Estas directivas ayudan a que el navegador haga antes lo importante, pero mal usadas pueden competir por ancho de banda.
- preconnect: adelanta DNS/TCP/TLS a un origen crítico (CDN, fonts, API). Útil cuando sabes que vas a pedir recursos pronto.
- preload: descarga anticipada de un recurso crítico (imagen LCP, fuente crítica, CSS crítico externo).
- fetchpriority: sugiere prioridad de descarga (especialmente útil para la imagen LCP).
Regla práctica: si un recurso define LCP o desbloquea el primer render, considera preload; si solo es “probable” pero no seguro, evita preloads agresivos.
Presupuestos de rendimiento y criterios de aceptación en despliegues
Define presupuestos (budgets) por plantilla
Un presupuesto convierte “rápido” en números verificables. Define budgets por tipo de página y por dispositivo (móvil primero). Ejemplo de budgets iniciales:
| Categoría | Budget sugerido | Motivo |
|---|---|---|
| LCP (lab) | ≤ 2.5–3.0s en perfil móvil simulado | Proxy de percepción de carga; útil para CI |
| INP (field) | p75 ≤ 200ms | Depende de CPU real y comportamiento; mejor en RUM |
| CLS (lab/field) | p75 ≤ 0.1 | Estabilidad visual; reproducible si reservas espacio |
| JS total (por ruta) | ≤ 170–250KB gzip (según app) | Correlaciona con CPU e INP |
| Nº requests críticos | Minimizar en el critical path | Reduce bloqueo y contención de red |
Convierte budgets en “gates” de CI/CD (paso a paso)
- Elige URLs canónicas de prueba por plantilla (3–5) y mantenlas estables.
- Define el entorno: misma región, mismo perfil de red/CPU, caché controlada.
- Ejecuta pruebas lab en cada PR o nightly (Lighthouse CI o WebPageTest) y guarda histórico.
- Fija umbrales: falla el build si LCP/CLS o peso de JS supera el budget (o si hay regresión > X%).
- Exige evidencia: en la PR, adjunta reporte antes/después cuando se toquen assets críticos.
- Monitorea field: tras desplegar, revisa RUM por release; si p75 empeora, activa rollback o hotfix.
Criterios de aceptación (Definition of Done) para cambios que afectan rendimiento
- CSS: no se añade CSS global sin justificar; se valida impacto en render-blocking y cobertura.
- JS: cualquier dependencia nueva requiere justificar tamaño, coste de CPU y alternativa; se valida code splitting.
- Imágenes: toda imagen above-the-fold tiene dimensiones; el hero no es lazy; se valida formato y
srcset. - Fuentes: se documenta estrategia (
font-display, preload si aplica) y se valida que no introduce CLS. - Terceros: todo script externo debe tener owner, propósito, condición de carga y budget asignado.
- Regresión: no se acepta un cambio que degrade budgets sin plan de mitigación y aprobación explícita.