Lazy loading con Intersection Observer en React

Resumen

Cargar imágenes solo cuando aparecen en pantalla es una de las técnicas más efectivas para mejorar el rendimiento de una aplicación web. Con la web API Intersection Observer y un componente de React, puedes implementar lazy loading real, ahorrar peticiones HTTP y entregar una experiencia más fluida al usuario.

Esta guía te muestra cómo conectar un observador al DOM dentro de un componente de React con TypeScript, manejar el ciclo de vida con useEffect y cambiar dinámicamente el src de la imagen.

¿Qué es Intersection Observer y por qué usarlo en React?

El Intersection Observer es una web API que detecta cuándo un elemento del DOM entra o sale del viewport. En lugar de escuchar eventos de scroll (que son costosos), delega esa tarea al navegador y te avisa solo cuando hay un cambio relevante.

¿Qué hace Intersection Observer? Observa un nodo del DOM y ejecuta un callback cada vez que ese nodo cruza el límite visible de la pantalla. Es ideal para lazy loading, animaciones al hacer scroll o métricas de visibilidad.

En un componente de React, esta API te permite retrasar la carga de una imagen hasta que sea visible, evitando requests innecesarios. [00:24]

¿Cómo crear el observador dentro de un componente?

El flujo tiene tres pasos claros que debes ejecutar en orden dentro del componente. [01:08]

  • Crear el observador con new IntersectionObserver.
  • Decirle al observador que observe el nodo referenciado.
  • Desconectar el observador cuando el componente se desmonte.

El constructor recibe un callback que entrega un array de entries. Cada entry trae la propiedad isIntersecting, que es true cuando el elemento ya entró al viewport.

tsx const observer = new window.IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { console.log('Hey, you!'); } }); });

Aquí TypeScript ayuda muchísimo: al escribir entry. te autocompleta las propiedades disponibles, así puedes programar casi sin abrir documentación. [02:52]

¿Por qué envolver todo dentro de useEffect?

En aplicaciones con server-side rendering como Next.js, el DOM no existe en el servidor. Por eso, el observador debe vivir en el cliente. La forma idiomática de garantizar eso en React es con useEffect y un array de dependencias vacío para que se ejecute una sola vez al montar el componente. [04:18]

Dentro del efecto debes verificar que node.current exista antes de llamar a observer.observe(node.current). Aunque parezca redundante, es la forma segura de evitar errores de TypeScript sin recurrir al operador bang (!).

¿Para qué sirve el operador ! en TypeScript? Le dice al compilador que confíe en ti y que el valor no será null ni undefined. Funciona, pero es preferible usar una verificación explícita con if para mantener la seguridad de tipos.

¿Cómo desconectar el observador correctamente?

Cada vez que un efecto crea una suscripción, debe limpiarla. Devuelve una función desde el useEffect que llame a observer.disconnect(). Así evitas fugas de memoria cuando el componente se desmonta o se re-renderiza. [05:30]

¿Cómo cambiar el src de la imagen solo cuando es visible?

Detectar la intersección es la mitad del trabajo. La otra mitad es renderizar la imagen real solo en ese momento. Para lograrlo, usa un estado local con useState que guarde el source actual.

  • Inicia el estado como un string vacío o un placeholder.
  • Cuando isIntersecting sea true, actualiza el estado con la URL real recibida vía prop.
  • Pasa ese estado al atributo src de la imagen.

Como el efecto usa el prop image, debes incluirlo en el array de dependencias del useEffect. Es una buena práctica que React y TypeScript te van a recordar. [07:45]

Al probarlo en el navegador y filtrar por imágenes en la pestaña Network, verás que cada request HTTP ocurre solo cuando el zorro correspondiente entra al viewport. Antes de eso, no se descarga nada.

¿Cómo mejorar el placeholder con una imagen base64?

Dejar un espacio en blanco mientras la imagen carga genera una experiencia pobre. Un truco que se usa en el componente Image de Next.js es renderizar una imagen transparente en base64 de 320x320 píxeles y pintarla con CSS. [09:30]

  • La imagen pesa prácticamente nada porque va embebida como data URL.
  • No genera request HTTP adicional.
  • Con Tailwind puedes aplicar un color de fondo gris para que el usuario perciba que algo está cargando.

Este detalle convierte un cuadro vacío en un skeleton funcional, lo que comunica al usuario que el contenido viene en camino.

Con el observador conectado, la referencia al nodo y el cambio dinámico de src, el componente RandomFox ya tiene la funcionalidad principal lista. ¿Qué le agregarías tú para hacerlo aún más reusable? Déjalo en los comentarios.

      Lazy loading con Intersection Observer en React