Presione ESC para cerrar

SEO Magazine La revista SEO ofrece las últimas actualizaciones sobre SEO, marketing digital, IA, consejos para agencias, nuevas tendencias y más.

Core Web Vitals: Guía Técnica Completa de Optimización 2025

Los Core Web Vitals son métricas esenciales que Google utiliza para evaluar la experiencia de usuario en tu sitio web y que impactan directamente en tus rankings de búsqueda. Desde la actualización «Page Experience» de 2021, optimizar estas métricas no es opcional si quieres competir en SERPs competitivos.

En esta guía técnica aprenderás qué son exactamente los Core Web Vitals, cómo medirlos correctamente, qué causa los problemas más comunes y cómo optimizar cada métrica paso a paso con soluciones técnicas específicas.

Qué son los Core Web Vitals

Core Web Vitals son un subconjunto de Web Vitals, iniciativa de Google para proporcionar métricas unificadas de calidad de experiencia de usuario esenciales para todos los sitios web.

Las tres métricas Core Web Vitals actuales:

LCP (Largest Contentful Paint): mide velocidad de carga percibida. Específicamente, cuánto tarda en renderizar el elemento de contenido más grande visible en el viewport.

FID (First Input Delay) / INP (Interaction to Next Paint): mide interactividad. FID medía el delay de la primera interacción. INP (que lo reemplazó en marzo 2024) mide la capacidad de respuesta general durante toda la vida de la página.

CLS (Cumulative Layout Shift): mide estabilidad visual. Cuánto se mueven inesperadamente los elementos mientras la página carga.

Por qué importan:

Son factores de ranking oficiales confirmados por Google. Impactan directamente la experiencia de usuario (UX). Sitios con buenos Core Web Vitals tienen menores tasas de rebote, mayor engagement y mejores conversiones. En mercados competitivos, pueden ser el diferenciador entre rankear #3 o #8.

Estado Actual: Umbrales y Objetivos

Google define umbrales específicos para cada métrica.

Largest Contentful Paint (LCP)

Objetivo: ≤ 2.5 segundos Necesita mejora: 2.5s – 4.0s Pobre: > 4.0 segundos

Qué mide: tiempo hasta que el elemento de contenido más grande (imagen, video, bloque de texto) se renderiza completamente en el viewport.

Por qué importa: usuarios perciben sitio como «cargado» cuando ven el contenido principal, no cuando todos los recursos terminan de cargar.

First Input Delay (FID) → Interaction to Next Paint (INP)

INP (nueva métrica desde marzo 2024):

Objetivo: ≤ 200 milisegundos Necesita mejora: 200ms – 500ms Pobre: > 500 milisegundos

Qué mide: latencia de todas las interacciones durante la vida de la página. Mide el tiempo desde que usuario interactúa (click, tap, key press) hasta que el navegador puede mostrar el siguiente frame con feedback visual.

Diferencia con FID: FID solo medía la primera interacción. INP evalúa todas las interacciones, proporcionando métrica más completa de responsividad.

Por qué importa: sitios que no responden inmediatamente frustran usuarios. Clicks que no hacen nada aparentemente causan abandono.

Cumulative Layout Shift (CLS)

Objetivo: ≤ 0.1 Necesita mejora: 0.1 – 0.25 Pobre: > 0.25

Qué mide: suma de todos los cambios de layout inesperados durante la vida de la página. No tiene unidad de tiempo, es puntuación.

Cálculo: Impact Fraction × Distance Fraction para cada shift.

Por qué importa: intentas hacer click en botón y de repente se mueve porque cargó un banner arriba. Experiencia horrible, especialmente en móvil.

Cómo Medir Core Web Vitals

Diferentes herramientas proporcionan diferentes tipos de datos.

Field Data (Datos de Campo – Usuarios Reales)

Datos de usuarios reales visitando tu sitio en condiciones reales (diferentes dispositivos, conexiones, ubicaciones).

Google Search Console:

  1. Search Console → Experience → Core Web Vitals
  2. Muestra URLs agrupadas por estado (Good, Needs Improvement, Poor)
  3. Separado por Mobile y Desktop
  4. Datos agregados de últimos 28 días

Ventaja: datos reales de TU audiencia específica.

Limitación: requiere volumen mínimo de tráfico. Sitios pequeños pueden no tener suficientes datos.

Chrome User Experience Report (CrUX):

Dataset público de datos de usuarios reales de Chrome.

Acceso:

  • CrUX Dashboard: https://lookerstudio.google.com/datasources/create (busca Chrome UX Report)
  • PageSpeed Insights muestra datos CrUX si disponibles
  • BigQuery para análisis avanzado

Ventaja: benchmark contra otros sitios, datos históricos.

Web Vitals Extension (Chrome):

Extensión que muestra Core Web Vitals de cualquier página mientras navegas.

Instalación: Chrome Web Store → «Web Vitals»

Uso: ícono en toolbar muestra métricas en tiempo real mientras visitas páginas.

Lab Data (Datos de Laboratorio – Simulados)

Datos de pruebas controladas en condiciones específicas. Útil para debugging pero no refleja experiencia real de usuarios.

Google PageSpeed Insights:

  1. https://pagespeed.web.dev
  2. Introduce URL
  3. Analiza Mobile y Desktop
  4. Muestra Field Data (si disponible) y Lab Data (siempre)

Ventaja: diagnóstico detallado con recomendaciones específicas. Análisis inmediato sin esperar acumulación de datos.

Limitación: condiciones simuladas pueden no reflejar tu audiencia real.

Lighthouse (Integrado en Chrome DevTools):

  1. Chrome → F12 → Lighthouse tab
  2. Selecciona categorías (Performance mínimo)
  3. Analiza Desktop o Mobile
  4. Generate report

Ventaja: puedes probar localmente durante desarrollo, antes de publicar cambios. Más control sobre condiciones de prueba.

WebPageTest:

https://www.webpagetest.org

Opciones avanzadas:

  • Selecciona ubicación geográfica específica
  • Tipo de conexión (3G, 4G, Cable)
  • Dispositivo específico
  • Múltiples runs para promedios
  • Video filmstrip de carga

Ventaja: testing más realista con throttling de red, múltiples ubicaciones.

GTmetrix:

https://gtmetrix.com

Similar a PageSpeed pero con interfaz más visual, histórico de pruebas, alertas cuando performance empeora.

Monitoreo Continuo (RUM – Real User Monitoring)

Herramientas que recopilan datos de usuarios reales continuamente.

web-vitals.js (Biblioteca de Google):

JavaScript library que mide Core Web Vitals en tu sitio y los envía a tu analytics.

import {onCLS, onINP, onLCP} from 'web-vitals';

onCLS(console.log);
onINP(console.log);
onLCP(console.log);

Integra con Google Analytics, custom endpoints, dataLayer.

Servicios de RUM comerciales:

  • SpeedCurve
  • Cloudflare Web Analytics
  • New Relic
  • Datadog RUM

Ventaja: monitoreo 24/7, alertas cuando métricas empeoran, segmentación por dispositivo/ubicación/navegador.

Optimización de LCP (Largest Contentful Paint)

LCP típicamente es el más problemático de los tres Core Web Vitals.

Identificar Tu Elemento LCP

Primero necesitas saber QUÉ elemento es tu LCP.

Chrome DevTools:

  1. F12 → Performance tab
  2. Click «Record» y recarga página
  3. Stop recording después de carga completa
  4. En timeline, busca «LCP» marker
  5. Click para ver qué elemento es

Elementos LCP comunes:

  • Hero image above the fold
  • Video thumbnail
  • Background image con CSS
  • Bloque de texto grande (en páginas text-heavy)

Estrategias de Optimización LCP

1. Optimizar Servidor y Hosting

TTFB (Time to First Byte) bajo:

TTFB es fundacional para LCP. Si servidor tarda 2 segundos en responder, imposible tener LCP < 2.5s.

Objetivo TTFB: < 600ms (bueno), < 200ms (excelente)

Cómo mejorar:

Hosting de calidad: VPS o cloud hosting en lugar de shared hosting sobrecargado.

CDN (Content Delivery Network): Cloudflare, Fastly, KeyCDN sirven contenido desde servidores geográficamente cercanos al usuario.

Server-side caching: página HTML pre-generada en lugar de construida dinámicamente en cada request.

Database optimization: queries eficientes, índices apropiados, caching de queries.

HTTP/2 o HTTP/3: multiplexing permite múltiples recursos en una conexión.

2. Optimizar Imágenes LCP

Si tu LCP es imagen (caso más común):

Formato moderno: WebP reduce tamaño 25-35% vs JPEG. AVIF reduce 50% pero menor soporte de navegadores.

Compresión apropiada: usa herramientas como Squoosh, TinyPNG. JPEG quality 75-85 es sweet spot.

Dimensiones correctas: no sirvas imagen 3000x2000px si muestras 1200x800px. Resize a tamaño de display.

Preload de imagen LCP:

<link rel="preload" as="image" href="hero-image.webp" fetchpriority="high">

Esto dice al navegador: «descarga esta imagen inmediatamente, es crítica».

fetchpriority=»high»:

<img src="hero.webp" alt="Hero" fetchpriority="high">

Navegadores modernos priorizan descarga de esta imagen sobre otras.

Evita lazy loading en imagen LCP:

<!-- MAL: imagen LCP con lazy loading -->
<img src="hero.webp" loading="lazy">

<!-- BIEN: carga eager (default) -->
<img src="hero.webp" loading="eager">

Lazy loading retrasa carga de imagen LCP, empeorando la métrica.

Responsive images con srcset:

<img 
  srcset="hero-400.webp 400w,
          hero-800.webp 800w,
          hero-1200.webp 1200w"
  sizes="(max-width: 600px) 400px,
         (max-width: 1000px) 800px,
         1200px"
  src="hero-800.webp"
  alt="Hero"
  fetchpriority="high">

Navegador descarga tamaño apropiado según viewport.

3. Optimizar Web Fonts

Si tu LCP es texto, web fonts pueden retrasarlo significativamente.

Preload de fuentes críticas:

<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>

font-display: swap:

@font-face {
  font-family: 'MyFont';
  src: url('font.woff2') format('woff2');
  font-display: swap;
}

Muestra fuente del sistema inmediatamente, cambia a custom font cuando carga. Evita FOIT (Flash of Invisible Text).

Limita número de fuentes: cada font family y weight es archivo adicional. Usa máximo 2 families, 3-4 weights total.

Self-host fonts: en lugar de Google Fonts, host localmente elimina DNS lookup y latencia externa.

4. Eliminar Recursos que Bloquean Render

CSS crítico inline:

Extrae CSS necesario para contenido above-the-fold y ponlo inline en <head>. Carga resto de CSS async.

<head>
  <style>
    /* Critical CSS inline */
    .hero { ... }
    .header { ... }
  </style>
  
  <link rel="preload" href="styles.css" as="style" onload="this.rel='stylesheet'">
  <noscript><link rel="stylesheet" href="styles.css"></noscript>
</head>

Defer JavaScript no crítico:

<!-- Bloquea render -->
<script src="script.js"></script>

<!-- No bloquea render -->
<script src="script.js" defer></script>

defer descarga script en paralelo sin bloquear parsing de HTML.

Async para scripts independientes:

<script src="analytics.js" async></script>

async descarga y ejecuta inmediatamente cuando está listo, sin esperar a DOM complete.

Regla: usa defer para scripts que interactúan con DOM, async para scripts independientes (analytics, ads).

5. Reduce Tamaño de Recursos

Minify CSS y JavaScript:

Remove whitespace, comments, código no usado.

Herramientas: Terser (JS), CSSNano (CSS), webpack/Vite/Parcel lo hacen automáticamente en build.

Tree shaking: elimina código JavaScript no utilizado en build.

Code splitting: divide JavaScript en chunks más pequeños, carga solo lo necesario.

// Lazy load componente solo cuando se necesita
import('./heavy-component.js').then(module => {
  module.init();
});

Compression (Gzip/Brotli):

Comprime texto (HTML, CSS, JS) antes de enviarlo.

Brotli es ~20% mejor que Gzip. Configura en servidor.

Apache (.htaccess):

<IfModule mod_deflate.c>
  AddOutputFilterByType DEFLATE text/html text/css application/javascript
</IfModule>

Nginx:

gzip on;
gzip_types text/css application/javascript;
brotli on;
brotli_types text/css application/javascript;

6. Optimiza Third-Party Scripts

Problema: scripts de terceros (analytics, ads, chat widgets) son incontrolables y frecuentemente lentos.

Estrategias:

Defer/async todo lo posible:

<script src="google-analytics.js" async></script>
<script src="chatbot.js" defer></script>

Lazy load widgets no críticos:

Carga chat widget solo cuando usuario scrollea o después de X segundos.

setTimeout(() => {
  const script = document.createElement('script');
  script.src = 'chatbot.js';
  document.body.appendChild(script);
}, 5000); // Carga después de 5 segundos

Self-host cuando posible:

En lugar de cargar Google Analytics desde google.com, descarga el script y sírvelo desde tu dominio. Elimina DNS lookup externo.

Usa facades: para embeds de YouTube, Facebook, muestra thumbnail estática, carga iframe real solo cuando usuario hace click.

Audit regular: revisa qué scripts third-party realmente necesitas. Elimina lo innecesario.

Checklist de Optimización LCP

  • ✓ TTFB < 600ms (preferible < 200ms)
  • ✓ CDN implementado
  • ✓ Server-side caching activo
  • ✓ Imagen LCP optimizada (WebP, comprimida, dimensiones correctas)
  • ✓ Preload en imagen/recurso LCP
  • ✓ fetchpriority=»high» en elemento LCP
  • ✓ NO lazy loading en elemento LCP
  • ✓ CSS crítico inline, resto async
  • ✓ JavaScript defer o async
  • ✓ Web fonts preloaded con font-display: swap
  • ✓ Third-party scripts diferidos
  • ✓ Gzip/Brotli compression habilitado

Optimización de INP (Interaction to Next Paint)

INP mide responsividad durante toda la sesión, no solo primera interacción.

Qué Causa INP Alto

Long tasks (tareas largas): JavaScript que bloquea el main thread > 50ms.

Event handlers pesados: código que se ejecuta en respuesta a clicks/taps tarda demasiado.

Layout thrashing: cambios de DOM que fuerzan recalculations repetidos de layout.

Third-party scripts: especialmente ads y analytics mal optimizados.

Identificar Problemas de INP

Chrome DevTools – Performance:

  1. F12 → Performance
  2. Record mientras interactúas con la página (clicks, scrolls, typing)
  3. Stop recording
  4. Busca «Long Tasks» (marcados en rojo si > 50ms)
  5. Identifica qué código causa long tasks

web-vitals.js con attribution:

import {onINP} from 'web-vitals/attribution';

onINP(({name, value, attribution}) => {
  console.log('INP:', value);
  console.log('Element:', attribution.interactionTarget);
  console.log('Event type:', attribution.interactionType);
});

Te dice exactamente qué interacción y elemento causan INP alto.

Estrategias de Optimización INP

1. Reduce y Optimiza JavaScript

Code splitting:

Divide JavaScript en chunks más pequeños. Carga solo código necesario para ruta actual.

Ejemplo con webpack:

// Lazy load modal component
button.addEventListener('click', async () => {
  const {Modal} = await import('./modal.js');
  new Modal().show();
});

Debounce/throttle en event handlers:

// MAL: ejecuta en cada keypress
input.addEventListener('input', () => {
  expensiveFunction();
});

// BIEN: ejecuta máximo cada 300ms
input.addEventListener('input', debounce(() => {
  expensiveFunction();
}, 300));

function debounce(func, wait) {
  let timeout;
  return function executedFunction(...args) {
    clearTimeout(timeout);
    timeout = setTimeout(() => func(...args), wait);
  };
}

Web Workers para tareas pesadas:

Mueve computación intensiva fuera del main thread.

// main.js
const worker = new Worker('worker.js');
worker.postMessage({data: largeDataset});
worker.onmessage = (e) => {
  console.log('Resultado:', e.data);
};

// worker.js
self.onmessage = (e) => {
  const result = heavyComputation(e.data);
  self.postMessage(result);
};

requestIdleCallback para tareas no urgentes:

requestIdleCallback(() => {
  // Código que puede esperar hasta que navegador esté idle
  analytics.sendData();
  preloadNextPage();
});

2. Optimiza Event Handlers

Event delegation:

En lugar de 100 listeners individuales, uno solo en contenedor padre.

// MAL: listener en cada botón
buttons.forEach(btn => {
  btn.addEventListener('click', handleClick);
});

// BIEN: un listener en container
container.addEventListener('click', (e) => {
  if (e.target.matches('button')) {
    handleClick(e);
  }
});

Passive event listeners:

// Dice al navegador que no llamarás preventDefault()
element.addEventListener('touchstart', handler, {passive: true});

Permite al navegador scrollear inmediatamente sin esperar a que handler termine.

Prioriza feedback visual inmediato:

button.addEventListener('click', () => {
  // Feedback visual inmediato
  button.classList.add('loading');
  
  // Lógica pesada después
  setTimeout(() => {
    expensiveOperation();
  }, 0);
});

3. Evita Layout Thrashing

Batch DOM reads y writes:

// MAL: causa múltiples recalculations
elements.forEach(el => {
  const height = el.offsetHeight; // Read
  el.style.height = height + 10 + 'px'; // Write
  // Repeat causes forced reflow cada vez
});

// BIEN: agrupa reads, luego writes
const heights = elements.map(el => el.offsetHeight); // All reads
elements.forEach((el, i) => {
  el.style.height = heights[i] + 10 + 'px'; // All writes
});

Usa CSS transforms en lugar de position/dimensions:

/* MAL: causa reflow */
.animated {
  position: relative;
  left: 100px;
}

/* BIEN: solo compositor */
.animated {
  transform: translateX(100px);
}

Transforms y opacity solo afectan compositor layer, no causan reflow/repaint.

requestAnimationFrame para cambios visuales:

// Sincroniza con refresh rate del navegador
function animate() {
  element.style.transform = `translateX(${x}px)`;
  requestAnimationFrame(animate);
}
requestAnimationFrame(animate);

4. Reduce Impacto de Third-Party Scripts

Carga diferida:

// Carga chat widget solo después de interacción
let chatLoaded = false;
document.addEventListener('scroll', () => {
  if (!chatLoaded) {
    loadChatWidget();
    chatLoaded = true;
  }
}, {once: true, passive: true});

Sandbox en iframes:

<iframe 
  src="third-party.html" 
  sandbox="allow-scripts"
  loading="lazy">
</iframe>

Limita lo que código third-party puede hacer.

Monitoring y accountability:

Usa Resource Timing API para medir cuánto tarda cada script third-party:

performance.getEntriesByType('resource')
  .filter(r => r.name.includes('analytics'))
  .forEach(r => console.log(r.name, r.duration));

Si script específico causa problemas, considera alternativa o eliminarlo.

5. Optimiza Rendering

CSS containment:

.card {
  contain: layout style paint;
}

Dice al navegador que cambios dentro de .card no afectan fuera. Optimiza reflows.

content-visibility:

.long-article section {
  content-visibility: auto;
}

Renderiza solo secciones visibles en viewport. Dramático improvement en páginas largas.

will-change (con cuidado):

.animated-element {
  will-change: transform;
}

Optimiza elemento para animación. No abuses (crea layers adicionales, consume memoria).

Checklist de Optimización INP

  • ✓ Total JavaScript bundle < 300KB (mobile)
  • ✓ No long tasks > 50ms durante interacciones críticas
  • ✓ Event handlers optimizados (delegation, debounce)
  • ✓ Third-party scripts diferidos o lazy loaded
  • ✓ Web Workers para tareas computacionales pesadas
  • ✓ DOM manipulation batched (no thrashing)
  • ✓ Transforms/opacity para animaciones (no layout properties)
  • ✓ Passive event listeners donde apropiado
  • ✓ Code splitting implementado
  • ✓ Feedback visual inmediato en interacciones

Optimización de CLS (Cumulative Layout Shift)

CLS mide estabilidad visual. Elementos que «saltan» mientras página carga.

Causas Comunes de CLS

Imágenes sin dimensiones: navegador no sabe cuánto espacio reservar, causa shift cuando carga.

Ads, embeds, iframes sin dimensiones: mismo problema.

Fonts que cargan tarde: FOIT o FOUT causa texto que salta.

Contenido inyectado dinámicamente: banners, notificaciones que pushean contenido existente.

Animaciones mal implementadas: cambios de height/width en lugar de transform.

Identificar Causas de CLS

Chrome DevTools:

  1. F12 → Performance
  2. Experience section → Layout Shifts
  3. Click en cada shift para ver qué elemento lo causó

web-vitals.js con attribution:

import {onCLS} from 'web-vitals/attribution';

onCLS(({value, attribution}) => {
  console.log('CLS:', value);
  attribution.largestShiftSource && console.log('Element:', attribution.largestShiftSource);
});

Layout Shift GIF Debugger (Chrome Extension):

Genera GIF animado mostrando exactamente qué elementos se mueven y cuándo.

Estrategias de Optimización CLS

1. Especifica Dimensiones de Imágenes y Videos

Siempre include width y height:

<!-- MAL: sin dimensiones -->
<img src="photo.jpg" alt="Photo">

<!-- BIEN: con dimensiones -->
<img src="photo.jpg" alt="Photo" width="800" height="600">

Navegador reserva espacio correcto basándose en aspect ratio incluso antes de cargar imagen.

Para responsive images, usa aspect-ratio CSS:

img {
  max-width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
}
<img src="photo.jpg" alt="Photo" style="aspect-ratio: 16/9">

Videos:

<video width="1920" height="1080" poster="thumbnail.jpg">
  <source src="video.mp4" type="video/mp4">
</video>

2. Reserva Espacio para Ads e Iframes

Contenedor con min-height:

.ad-slot {
  min-height: 250px; /* Altura típica de tu ad */
}
<div class="ad-slot">
  <!-- Ad code aquí -->
</div>

Placeholder con aspect ratio:

.embed-container {
  position: relative;
  padding-bottom: 56.25%; /* 16:9 aspect ratio */
  height: 0;
  overflow: hidden;
}

.embed-container iframe {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}
<div class="embed-container">
  <iframe src="youtube-video"></iframe>
</div>

3. Optimiza Web Fonts

font-display: swap o optional:

@font-face {
  font-family: 'CustomFont';
  src: url('font.woff2') format('woff2');
  font-display: swap; /* Muestra fallback, luego cambia */
  /* O */
  font-display: optional; /* Usa custom si carga rápido, sino fallback */
}

Preload fonts críticos:

<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>

Subset fonts: incluye solo caracteres que usas. Reduce tamaño = carga más rápida.

Usa variable fonts: un archivo con múltiples weights en lugar de archivo por weight.

4. Evita Insertar Contenido Arriba de Contenido Existente

Banners/notificaciones:

/* MAL: pushea contenido */
.banner {
  position: relative;
}

/* BIEN: overlay sin pushear */
.banner {
  position: fixed;
  top: 0;
  width: 100%;
  z-index: 1000;
}

O usa position: sticky si quieres que scrolle con página pero no pushee contenido inicial.

Contenido dinámico:

Si cargas contenido vía AJAX y lo insertas, hazlo al final o en contenedor con altura definida.

// MAL: inserta al principio, pushea todo
container.prepend(newContent);

// BIEN: inserta al final
container.append(newContent);

// O: contenedor con altura fija que no cambia

5. Animaciones No Deben Cambiar Layout

Usa transforms, no top/left/width/height:

/* MAL: causa layout shift */
.slide-in {
  animation: slideIn 0.3s;
}
@keyframes slideIn {
  from { left: -100px; }
  to { left: 0; }
}

/* BIEN: solo compositor */
.slide-in {
  animation: slideIn 0.3s;
}
@keyframes slideIn {
  from { transform: translateX(-100px); }
  to { transform: translateX(0); }
}

Properties que NO causan layout:

  • transform
  • opacity

Properties que SÍ causan layout:

  • width, height
  • margin, padding
  • top, left, right, bottom
  • font-size

6. Manejo de Contenido Above-the-Fold

Skeleton screens:

En lugar de espacio vacío que salta cuando carga contenido, muestra placeholder con forma similar.

<div class="skeleton">
  <div class="skeleton-header"></div>
  <div class="skeleton-text"></div>
  <div class="skeleton-text"></div>
</div>
.skeleton {
  background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
  background-size: 200% 100%;
  animation: loading 1.5s infinite;
}
@keyframes loading {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

Usuario ve que algo está cargando, espacio está reservado.

Progressive rendering:

Muestra estructura básica inmediatamente, enhancement después.

<!-- Carga inmediata: estructura básica -->
<article>
  <h1>Title</h1>
  <div id="content-placeholder" style="min-height: 500px">
    Loading...
  </div>
</article>

<script>
// Contenido rich después
fetch('/api/content').then(html => {
  document.getElementById('content-placeholder').innerHTML = html;
});
</script>

Checklist de Optimización CLS

  • ✓ Todas las imágenes tienen width/height o aspect-ratio
  • ✓ Videos especifican dimensiones
  • ✓ Ads e iframes tienen espacio reservado (min-height)
  • ✓ Web fonts con font-display: swap/optional
  • ✓ Fonts críticos preloaded
  • ✓ No hay conteni

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *