Cómo hacer lazy loading de imágenes en React sin afectar el LCP
Para aplicar lazy loading a una imagen en React, renderiza un elemento img normal con
loading="lazy". Mantén la URL real en src, agrega srcSet y sizes cuando haya variantes
responsivas disponibles, y proporciona width y height para que el navegador pueda reservar
espacio.
La excepción importante es la imagen que probablemente se convierta en el elemento Largest Contentful Paint (LCP) de la página. Mantén esa imagen en carga eager y detectable en el HTML inicial. Aplicarle lazy loading retrasa la solicitud que la página más necesita completar rápido.
| Rol de la imagen | Decisión de carga | Otro marcado importante |
|---|---|---|
| Imagen hero o probable LCP | Eager | fetchPriority="high" cuando se haya verificado |
| Otra imagen visible en el primer viewport | Eager | Dimensiones y fuentes responsivas |
| Imagen de contenido común fuera de pantalla | Lazy | src, srcSet, sizes y dimensiones |
| Fondo decorativo en CSS | Mecanismo aparte | No sustituyas contenido img significativo |
«Above the fold» no es un número fijo de imágenes. La altura del viewport, los diseños responsivos, los banners y el contenido localizado pueden mover la misma imagen dentro o fuera del viewport inicial. Clasifica las imágenes según la página renderizada y luego verifica la decisión en tamaños de pantalla representativos.
Usa la prop nativa loading de React
React pasa la prop loading al elemento de imagen nativo del navegador. No necesitas un efecto, un
manejador de scroll ni una biblioteca de Intersection Observer para una imagen común fuera de
pantalla.
import type { ReactNode } from 'react'
interface ResponsiveImageProps {
alt: string
height: number
sizes: string
src: string
srcSet: string
width: number
}
export function ResponsiveImage({
alt,
height,
sizes,
src,
srcSet,
width,
}: ResponsiveImageProps): ReactNode {
return (
<img
className="responsive-image"
src={src}
srcSet={srcSet}
sizes={sizes}
width={width}
height={height}
loading="lazy"
decoding="async"
alt={alt}
/>
)
}
Mantén una URL de imagen real en src. Mover la única URL a un atributo personalizado
data-src hace que la solicitud dependa de tu JavaScript e impide que el preload scanner del
navegador la descubra en el marcado de imagen normal. Además, deja la imagen sin cargar si el script
falla.
La prop decoding="async" es una sugerencia aparte sobre la decodificación. No decide cuándo comienza la
solicitud de red ni reemplaza a loading="lazy".
Usa el componente en una lista igual que cualquier otro componente de React:
<ResponsiveImage
src="/images/workshop-960.webp"
srcSet="/images/workshop-480.webp 480w, /images/workshop-960.webp 960w"
sizes="(max-width: 40rem) 100vw, 40rem"
width={960}
height={640}
alt="A technician calibrating a camera rig"
/>
El lazy loading solo cambia cuándo comienza la solicitud. No redimensiona, comprime, convierte, almacena en caché ni describe la imagen.
Mantén la imagen LCP en carga eager
No apliques loading="lazy" a una imagen que probablemente sea la LCP. Renderiza su URL en el HTML
inicial cuando tu framework de React admita renderizado en el servidor, para que el navegador pueda
descubrirla sin esperar al renderizado en el cliente ni a otra solicitud de datos.
import type { ReactNode } from 'react'
export function HeroImage(): ReactNode {
return (
<img
className="responsive-image"
src="/images/harbor-1280.webp"
srcSet="/images/harbor-640.webp 640w, /images/harbor-1280.webp 1280w"
sizes="100vw"
width={1280}
height={720}
loading="eager"
fetchPriority="high"
alt="Fishing boats returning to the harbor at sunrise"
/>
)
}
fetchPriority="high" es una sugerencia de prioridad relativa, no una garantía sobre el orden de las
solicitudes. Resérvalo para la imagen LCP medida. Si se marcan varias imágenes con prioridad alta,
el navegador tiene información menos útil para decidir cuál importa más.
Una imagen puede ser el elemento LCP en móvil pero no en escritorio, o al revés. Usa datos de rendimiento de campo para confirmar qué elemento se convierte en el LCP para visitantes reales, en lugar de suponer que toda imagen grande o diapositiva de carrusel es crítica.
Combina el lazy loading con imágenes responsivas
Una solicitud diferida todavía puede descargar un archivo innecesariamente grande. Con descriptores
de ancho como 480w, srcSet aporta los archivos candidatos y sizes describe el ancho del
espacio renderizado. El navegador combina esa información con la densidad de píxeles del dispositivo
y elige un candidato.
Escribe sizes pensando en el diseño, no en el archivo de origen. Si una imagen ocupa todo el
viewport en un teléfono y una columna de 640 píxeles en pantallas más anchas, indícalo así:
<img
className="responsive-image"
src="/images/product-960.webp"
srcSet="/images/product-480.webp 480w, /images/product-960.webp 960w"
sizes="(max-width: 40rem) 100vw, 40rem"
width={960}
height={640}
loading="lazy"
decoding="async"
alt="Red trail shoe viewed from the side"
/>
El navegador ejecuta la misma selección de candidatos responsivos para imágenes eager y lazy. La
decisión de carga no elimina la necesidad de candidatos correctos ni de un valor sizes preciso.
Reserva espacio para evitar saltos de diseño
Proporciona width y height para cada imagen de contenido. Los navegadores usan esos atributos
para calcular una relación de aspecto antes de que la imagen se haya descargado, reservando la
cantidad correcta de espacio en el diseño. Tu CSS todavía puede hacer que la imagen sea fluida:
.responsive-image {
display: block;
height: auto;
max-width: 100%;
}
Los atributos deben describir la relación de aspecto intrínseca de la imagen. Por ejemplo, un
img puede usar width={960} y height={640} cuando todos los candidatos tienen una relación 3:2,
incluso si el navegador selecciona un archivo de 480×320. Si las fuentes con dirección de arte usan
recortes o relaciones de aspecto distintas, sus dimensiones deben describir la fuente seleccionada y
no un respaldo no relacionado.
Reservar espacio para la imagen evita una fuente de Cumulative Layout Shift (CLS), no todos los desplazamientos posibles. Los pies de foto, los anuncios, los controles de consentimiento, las fuentes tipográficas y los mensajes de error todavía pueden mover la página.
Cuándo es apropiado Intersection Observer
Usa el comportamiento de carga nativo del navegador para las imágenes comunes. Recurre a
Intersection Observer solo cuando el componente necesite un comportamiento que loading="lazy" no
puede expresar, como iniciar una animación, registrar un evento de visibilidad o aplicar un fondo
decorativo costoso poco antes de que llegue al viewport.
No montes de forma condicional una imagen de contenido normal solo después de que se dispare un
observer, a menos que ese comportamiento sea realmente necesario. El montaje condicional retrasa el
descubrimiento, añade una vía de fallo de JavaScript y te obliga a diseñar un respaldo estable. Las
imágenes significativas deben seguir siendo elementos img o picture con texto alternativo útil.
React.lazy() resuelve un problema distinto: difiere la carga de un módulo de componente de
JavaScript. No difiere automáticamente las solicitudes de imagen que ese componente renderiza.
Genera derivados responsivos con Transloadit
El lazy loading no puede arreglar una imagen de origen demasiado grande. Un pipeline de entrega práctico genera un conjunto pequeño y acotado de anchos, optimiza cada resultado, los exporta a un almacenamiento duradero y registra sus URL y dimensiones para la vista de React.
El Robot /image/resize de Transloadit puede crear esas variantes de ancho, y el Robot /image/optimize puede optimizar los resultados compatibles:
{
"steps": {
"small": {
"robot": "/image/resize",
"use": ":original",
"width": 480,
"resize_strategy": "fit",
"zoom": false,
"format": "webp"
},
"medium": {
"robot": "/image/resize",
"use": ":original",
"width": 960,
"resize_strategy": "fit",
"zoom": false,
"format": "webp"
},
"large": {
"robot": "/image/resize",
"use": ":original",
"width": 1440,
"resize_strategy": "fit",
"zoom": false,
"format": "webp"
},
"small_optimized": {
"robot": "/image/optimize",
"use": "small"
},
"medium_optimized": {
"robot": "/image/optimize",
"use": "medium"
},
"large_optimized": {
"robot": "/image/optimize",
"use": "large"
}
}
}
El ejemplo usa zoom: false para que una subida pequeña no se amplíe solo para alcanzar un ancho
objetivo. Después del procesamiento, agrega un Robot de almacenamiento u otro Step de exportación
adecuado para tu arquitectura y luego usa esas URL de entrega duraderas en src y srcSet. Las URL
de resultado de las Assemblies son salidas de procesamiento temporales, no almacenamiento permanente
de la aplicación. Consulta el servicio de exportación de
archivos para ver las integraciones de almacenamiento disponibles. No
esperes a que el componente de React cree los derivados. Si prefieres transformar y almacenar en
caché anchos aprobados bajo demanda, consulta la guía para servir imágenes responsivas desde una
sola URL.
La aplicación de React sigue siendo responsable de las decisiones de presentación: qué imagen es significativa, su texto alternativo, el tamaño del espacio en que se renderiza y si es crítica desde el inicio. Transloadit se encarga de la etapa de procesamiento de medios, en lugar de decidir qué imagen se convierte en el LCP en un diseño de página concreto.
Mide el resultado
Revisa la página en producción en lugar de confiar únicamente en el código fuente del componente:
- Graba cargas con viewport estrecho y ancho, con caché en frío, en las herramientas para desarrolladores del navegador.
- Confirma que la solicitud de la imagen LCP comience temprano y no esté marcada como lazy.
- Desplázate y verifica que las imágenes comunes fuera de pantalla se difieran sin que aparezcan tarde para el usuario.
- Inspecciona el candidato de
srcSetseleccionado y compara sus dimensiones con el espacio renderizado. - Mide el LCP y el CLS con datos de campo, agrupados por plantilla de página y viewport relevante.
- Bloquea las solicitudes de imagen para verificar que el texto alternativo sea útil y que los estados de fallo sean estables. Cuando la página admita renderizado en el servidor, inspecciona también el resultado renderizado en el servidor antes de la hidratación.
Las distancias y la planificación del lazy loading del navegador dependen de la implementación. Evita reglas como «aplica lazy loading a todo lo que venga después de la tercera imagen» o un umbral fijo de píxeles supuesto. Mide las páginas y los dispositivos que tus usuarios realmente reciben.
Errores comunes de lazy loading en React
- Aplicar lazy loading a la imagen hero o a la imagen LCP medida.
- Mover la única URL de la imagen de
srcadata-src. - Renderizar imágenes críticas solo después de que se complete el código del lado del cliente o una solicitud de datos.
- Proporcionar
srcSetsin un valorsizespreciso. - Omitir
widthyheightporque al final el CSS controla el tamaño de la imagen. - Marcar todas las imágenes con
fetchPriority="high". - Usar una biblioteca de observer solo para reproducir el comportamiento nativo del navegador.
- Suponer que
React.lazy()controla las solicitudes de red de las imágenes. - Diferir una fuente de 3.000 píxeles en lugar de generar un derivado adecuado.
El valor predeterminado confiable es sencillo: renderiza marcado de imagen completo, mantén en carga eager las imágenes que probablemente sean LCP, aplica lazy loading a las imágenes comunes fuera de pantalla y haz que cada archivo solicitado sea apropiado para el espacio que lo muestra.
Para conocer las reglas subyacentes del navegador, consulta el estándar de lazy loading de HTML, la guía de LCP de web.dev y su guía de imágenes responsivas.
