La forma más rápida de empeorar el rendimiento de una web es aplicar lazy loading a todo. La imagen de cabecera suele ser el elemento que Google mide como Largest Contentful Paint, y diferir su descarga estropea justo la métrica que se quería mejorar. Aquí va la implementación paso a paso, los casos en los que el atributo nativo no basta y, para quien trabaje con WordPress, qué hace ya el núcleo sin que nadie se lo pida. Si buscas la introducción al concepto, está en qué es el lazy loading y cómo funciona.
Primero: identifica el LCP y déjalo en paz
Antes de tocar nada hay que saber qué elemento es el Largest Contentful Paint de cada plantilla, que en la mayoría de las páginas es la imagen destacada o el héroe. Ese no lleva loading="lazy" nunca. Al contrario: se le marca prioridad alta para que el navegador lo pida antes que el resto.
<!-- El LCP: prioridad alta, sin lazy -->
<img src="cabecera-1200.jpg" alt="Descripción"
width="1200" height="630"
fetchpriority="high" decoding="async">
<!-- El resto: diferido -->
<img src="foto-800.jpg" alt="Descripción"
width="800" height="600"
loading="lazy" decoding="async">
El decoding="async" permite al navegador descodificar la imagen sin bloquear el hilo principal, y el width y el height reservan el espacio para que no haya salto de maquetación cuando la imagen aparece.
Lo que WordPress ya hace solo
Esta parte se la salta casi todo el mundo y explica bastantes desastres. El núcleo de WordPress lleva años gestionando esto por su cuenta:
- Desde la versión 5.5 añade
loading="lazy"de forma automática a las imágenes del contenido. - Desde la 5.9 deja de aplicarlo a la primera imagen, para no perjudicar al LCP.
- Desde la 6.3 ese umbral pasó de una a tres imágenes, pensando en las maquetas a varias columnas donde hay más de una pieza visible al entrar. Se ajusta con el filtro
wp_omit_loading_attr_threshold. - También desde la 6.3, el núcleo marca
fetchpriority="high"en la primera imagen que supere un tamaño mínimo, calculado como ancho por alto con un umbral por defecto de 50.000 píxeles. Una imagen de 200 por 200 no lo recibe. El filtro eswp_min_priority_img_pixels.
De ahí sale el fallo más frecuente en webs con WordPress: un plugin de optimización que vuelve a aplicar lazy loading a todas las imágenes, incluidas esas tres primeras, y deshace el trabajo que el núcleo ya había hecho. El síntoma es un LCP que empeora justo después de instalar el plugin que iba a arreglarlo. Comprobarlo es abrir el código fuente y mirar si la imagen de cabecera lleva loading="lazy": si lo lleva, hay algo pisándose.
El atributo nativo cubre casi todo
Para el resto de imágenes e iframe, el atributo loading="lazy" basta. No necesita librerías, los navegadores que no lo entienden simplemente lo ignoran y no hay nada que mantener.
<img src="foto-800.jpg" alt="Descripción" loading="lazy"
width="800" height="600">
<iframe src="https://player.example" loading="lazy"
width="560" height="315" title="Título del vídeo"></iframe>
Conviene el title en el iframe, que es lo que anuncia el lector de pantalla cuando llega a ese marco.
Cuándo hace falta JavaScript
El atributo nativo no llega a tres sitios: los fondos declarados en CSS con background-image, los marcadores de posición de baja calidad y los vídeos que deben empezar a cargar al acercarse. Para eso está IntersectionObserver.
const imgs = document.querySelectorAll('img[data-src]');
if ('IntersectionObserver' in window) {
const io = new IntersectionObserver((entries, obs) => {
entries.forEach(e => {
if (!e.isIntersecting) return;
const img = e.target;
img.src = img.dataset.src;
img.removeAttribute('data-src');
obs.unobserve(img);
});
}, { rootMargin: '200px 0px' });
imgs.forEach(i => io.observe(i));
} else {
imgs.forEach(i => i.src = i.dataset.src);
}
El rootMargin de 200 píxeles hace que la carga empiece antes de que la imagen entre en pantalla, que es lo que evita el hueco en blanco cuando alguien baja rápido. Y solo en este caso, el de la carga por JavaScript, tiene sentido el noscript con la imagen final: con el atributo nativo no hace falta, porque el HTML ya trae el src real.
Vídeos y embeds de terceros
Para <video> propio, preload="metadata" o none y un poster como imagen fija evitan descargar el archivo entero a quien no va a darle al play. Para los embeds de YouTube o Vimeo, el patrón que más ahorra es la fachada: una imagen con botón de reproducción que inyecta el iframe real solo al hacer clic.
La diferencia no es menor. Un embed de vídeo arrastra su propio JavaScript, sus cookies y varias peticiones a dominios de terceros antes de que nadie haya decidido ver nada. Con la fachada, todo eso ocurre después del clic y solo para quien lo pide.
Reservar el espacio para que no salte nada
Una imagen diferida que aparece de golpe empuja el texto hacia abajo y eso cuenta como Cumulative Layout Shift. Se evita declarando width y height en el HTML, o reservando la proporción con aspect-ratio en CSS. Con srcset y sizes encima, cada dispositivo se descarga solo la versión que necesita:
<img src="img-800.jpg"
srcset="img-400.jpg 400w, img-800.jpg 800w, img-1200.jpg 1200w"
sizes="(max-width: 600px) 100vw, 50vw"
width="1200" height="800"
loading="lazy" decoding="async" alt="Descripción">
Los cinco errores que lo rompen
- Diferir el LCP: empeora exactamente la métrica que se quería mejorar.
- No reservar espacio: cada imagen que aparece mueve la página y suma CLS.
- Duplicar el trabajo del núcleo: un plugin que vuelve a aplicar lazy sobre lo que WordPress ya había decidido.
- Cargar por JavaScript sin alternativa: si el script falla o no se ejecuta, esas imágenes no existen para nadie.
- Dejar los embeds sin fachada: es donde más peso de terceros entra sin que nadie lo haya pedido.
La comprobación final es siempre la misma: medir antes y después con Lighthouse y con los datos de campo de Core Web Vitals, mirando LCP y CLS por separado. Si el LCP empeora tras aplicar lazy loading, casi siempre es que se ha diferido algo que debía cargar primero.
Preguntas frecuentes
¿Puedo aplicar lazy loading a la imagen principal?
No. Si esa imagen es el Largest Contentful Paint, diferirla retrasa su descarga y empeora la métrica. A los elementos críticos se les pone fetchpriority="high", nunca loading="lazy".
¿Hace falta un plugin de lazy loading en WordPress?
En la mayoría de los casos no. El núcleo lo aplica solo desde la versión 5.5, se salta las tres primeras imágenes desde la 6.3 y marca prioridad alta en la que parece el LCP. Un plugin que reaplique lazy a todo puede deshacer justo eso.
¿Cuándo necesito IntersectionObserver?
Cuando el atributo nativo no llega: fondos declarados con background-image en CSS, marcadores de baja calidad y vídeos que deben empezar a cargar al acercarse al viewport.
¿Cómo se difiere un vídeo de YouTube?
Con una fachada: una imagen con botón de reproducción que inserta el iframe real al hacer clic. Así el JavaScript, las cookies y las peticiones a terceros llegan solo cuando alguien decide ver el vídeo.
¿Sigue haciendo falta el noscript?
Solo con la carga por JavaScript, donde el src real llega desde un data-src. Con loading="lazy" el HTML ya trae la imagen final y el noscript sobra.
Fuentes
- Make WordPress Core, Image performance enhancements in WordPress 6.3





