Rendimiento y Core Web Vitals para SEO técnico: métricas y acciones

Capítulo 10

Tiempo estimado de lectura: 12 minutos

+ Ejercicio

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.

Continúa en nuestra aplicación.
  • Escuche el audio con la pantalla apagada.
  • Obtenga un certificado al finalizar.
  • ¡Más de 5000 cursos para que explores!
O continúa leyendo más abajo...
Download App

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)

  1. Define el scope: plantillas críticas (home, categoría, ficha, artículo, checkout) y dispositivos objetivo (móvil primero).
  2. Recoge field: p75 de LCP/INP/CLS por plantilla y por país/dispositivo si aplica.
  3. Reproduce en lab: elige 3–5 URLs representativas por plantilla; ejecuta 3–5 corridas y toma medianas.
  4. Correlaciona: si field es malo pero lab es bueno, sospecha de terceros, rutas específicas, long tasks por hidratación, o variabilidad de red.
  5. 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).

DisparadorCómo se manifiestaCómo confirmarlo
TTFB alto (backend, caché, CDN)La carga empieza tarde; todo se desplazaDevTools Network: Waiting (TTFB) alto; logs de servidor; trazas
Render-blocking CSSHTML llega, pero el render se retrasaWaterfall: CSS en el critical path; Performance: tiempo hasta First Paint alto
JS bloqueante / hidratación pesadaEl main thread está ocupado antes de pintarPerformance: long tasks; scripting alto antes del LCP
Imagen LCP sin optimizarLCP es una imagen que tarda en descargarse/decodificarNetwork: tamaño, prioridad, decode; Performance: Largest Contentful Paint apunta a la imagen
Fuentes web bloqueantesTexto LCP tarda por FOIT/FOUT o reflowWaterfall de fuentes; CSS @font-face; Layout shifts asociados
Terceros en el headBloquean o compiten por ancho de bandaNetwork: requests a dominios externos temprano; CPU por scripts

Guía práctica para diagnosticar LCP (paso a paso):

  1. En DevTools > Performance, graba una carga en modo incógnito con caché desactivada.
  2. Identifica el evento LCP y el elemento asociado (imagen o bloque de texto).
  3. 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.
  4. Revisa el critical path: CSS bloqueante, scripts en <head>, y long tasks antes del LCP.
  5. 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.

DisparadorEjemploCómo confirmarlo
Long tasks (> 50ms)Parse/compile de bundles grandesPerformance: bloques largos de scripting; Main thread saturado
Hidratación masivaSSR con hidratación completa al cargarPerformance: scripting alto tras load; interacciones tempranas lentas
Re-render costosoListas grandes sin virtualizaciónReact/Vue devtools + Performance; recálculo de estilo/layout
Handlers pesadosClick dispara lógica sin dividirPerformance: evento de input seguido de scripting y paint tardío
TercerosTag manager ejecuta scripts en interacciónNetwork/Performance: tareas atribuibles a scripts externos

Guía práctica para diagnosticar INP (paso a paso):

  1. Abre DevTools > Performance y activa “Web Vitals” si está disponible.
  2. Graba y realiza interacciones representativas (abrir menú, filtrar, añadir al carrito).
  3. Localiza el evento de interacción con mayor latencia y revisa la cadena: input delay, processing time, presentation delay.
  4. Identifica qué funciones consumen CPU (call tree) y qué scripts aportan long tasks (incluidos terceros).
  5. 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.

DisparadorEjemploCómo confirmarlo
Sin width/height o aspect-ratioImagen hero empuja el texto al cargarDevTools: Layout Shift Regions; auditorías de CLS
Ads/iframes dinámicosSlot publicitario colapsado que luego se expandeShift ocurre tras requests a terceros
Consent bannerInserción arriba del contenidoShift al aparecer el banner; revisa DOM mutations
Fuentes webFOIT/FOUT cambia el tamaño del textoShift coincide con carga de fuentes; waterfall de fonts
Contenido inyectadoRecomendaciones “related” insertadasShift tras fetch; mutation observer en RUM

Guía práctica para diagnosticar CLS (paso a paso):

  1. En DevTools > Performance, graba una carga completa y observa “Layout Shifts”.
  2. Haz click en cada shift para ver los nodos afectados y las regiones resaltadas.
  3. Identifica si el shift proviene de: imágenes sin tamaño, iframes, fuentes o inserciones tardías.
  4. 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-Control correcto 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, immutable

2) 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 defer para JS propio no crítico; async para 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/sizes para 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 requestIdleCallback o 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 swap o optional para 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/height o aspect-ratio en 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/async y 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íaBudget sugeridoMotivo
LCP (lab)≤ 2.5–3.0s en perfil móvil simuladoProxy de percepción de carga; útil para CI
INP (field)p75 ≤ 200msDepende de CPU real y comportamiento; mejor en RUM
CLS (lab/field)p75 ≤ 0.1Estabilidad visual; reproducible si reservas espacio
JS total (por ruta)≤ 170–250KB gzip (según app)Correlaciona con CPU e INP
Nº requests críticosMinimizar en el critical pathReduce bloqueo y contención de red

Convierte budgets en “gates” de CI/CD (paso a paso)

  1. Elige URLs canónicas de prueba por plantilla (3–5) y mantenlas estables.
  2. Define el entorno: misma región, mismo perfil de red/CPU, caché controlada.
  3. Ejecuta pruebas lab en cada PR o nightly (Lighthouse CI o WebPageTest) y guarda histórico.
  4. Fija umbrales: falla el build si LCP/CLS o peso de JS supera el budget (o si hay regresión > X%).
  5. Exige evidencia: en la PR, adjunta reporte antes/después cuando se toquen assets críticos.
  6. 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.

Ahora responde el ejercicio sobre el contenido:

Si los datos de campo (p75) muestran un INP malo pero en laboratorio el INP sale bueno, ¿qué explicación es más consistente y qué conviene investigar primero?

¡Tienes razón! Felicitaciones, ahora pasa a la página siguiente.

¡Tú error! Inténtalo de nuevo.

Si field es malo y lab es bueno, lo más probable es variabilidad real (terceros, rutas, red/CPU) o long tasks, por ejemplo por hidratación. El siguiente paso es aislar terceros y perfilar el main thread para encontrar tareas largas y handlers costosos.

Siguiente capítulo

Optimización de imágenes para SEO técnico: formatos, carga y accesibilidad

Arrow Right Icon
Portada de libro electrónico gratuitaSEO técnico esencial para desarrolladores web
67%

SEO técnico esencial para desarrolladores web

Nuevo curso

15 páginas

Descarga la aplicación para obtener una certificación gratuita y escuchar cursos en segundo plano, incluso con la pantalla apagada.