Next/Image: Optimización automática, lazy loading y placeholders explicados con ejemplos

DUGLAS MORENODUGLAS MORENO 👁 7

Introducción

Si estás construyendo una aplicación con Next.js y te preocupa el rendimiento de las imágenes, seguramente ya escuchaste hablar del componente next/image. Este elemento no es solo un <img> cualquiera: incorpora optimizaciones automáticas de tamaño, formato y compresión, además de lazy loading integrado y varios tipos de placeholders que mejoran la experiencia de carga.

En este artículo vas a aprender:

  • Qué hace qué hace automáticamente
  • cómo funciona lazy loading de forma nativa?
  • Qué placeholders están disponibles y cuándo usarlos
  • Cómo configurar el componente para diferentes casos de uso
  • Qué errores comunes evitar y cuáles son las mejores prácticas

El contenido está pensado para desarrolladores que ya conocen los básicos de Next.js (rutas, páginas, componentes) y quieren profundizar en la optimización de recursos visuales. Al final tendrás ejemplos listos para copiar y pegar, con la salida HTML esperada para que puedas verificar el comportamiento.


¿Qué hace realmente next/image?

Cuando Next.js compila tu proyecto, el componente next/image se transforma en un conjunto de elementos <img> y <picture> que apuntan a versiones optimizadas de la misma imagen. Estas versiones se generan en tiempo de compilación (si la imagen está en el proyecto) o en tiempo de ejecución mediante un servidor de imágenes (el llamado Image Optimizer).

Las optimizaciones incluyen:

  • Redimensionamiento automático: se crean múltiples anchuras según el dispositivo y el layout definido.
  • Formato moderno: se sirve WebP o AVIF cuando el navegador lo soporta, fallback a JPEG/PNG.
  • Compresión inteligente: se ajusta la calidad para equilibrar peso y fidelidad visual.
  • Lazy loading: el atributo loading='lazy' se agrega automáticamente, salvo que indiques lo contrario.
  • Placeholder opcional: puedes mostrar una versión borrosa, un color dominante o un espacio vacío mientras se carga la imagen real.

Todo esto ocurre sin que tengas que escribir un loader personalizado o configurar un CDN de imágenes por tu cuenta.


Lazy loading incorporado

El lazy loading (carga diferida) pospone la descarga de una imagen hasta que está próxima a entrar al viewport. En navegadores modernos basta con el atributo loading='lazy'. Next.js lo agrega por defecto en todas las imágenes que no tengan la propiedad priority establecida en true.

Esto significa que, si tienes una lista de productos con muchas imágenes, solo las que están visibles se descargan inicialmente; el resto se cargará cuando el usuario haga scroll.

Ejemplo básico

function ImgEjemplo({src, alt, width, height}) {
  return `<img src='${src}' alt='${alt}' width='${width}' height='${height}' loading='lazy' style='display:block;max-width:100%;height:auto;' />`;
}
console.log(ImgEjemplo({src:'/producto.jpg', alt:'Zapatillas', width:400, height:300}));

Salida esperada:

Zapatillas

Si quisieras forzar la carga inmediata (por ejemplo, una imagen hero en la parte superior), usarías priority:

function ImgPrioritaria({src, alt, width, height}) {
  return `<img src='${src}' alt='${alt}' width='${width}' height='${height}' loading='eager' style='display:block;max-width:100%;height:auto;' />`;
}
console.log(ImgPrioritaria({src:'/hero.jpg', alt:'Banner principal', width:1200, height:400}));

Salida esperada:

Banner principal

Placeholders: mejorar la percepción de velocidad

Mientras la imagen de alta resolución se descarga, es útil mostrar algo que indique que hay contenido cargándose. next/image ofrece tres estrategias de placeholder:

  1. blur – una versión muy pequeña y borrosa de la imagen (generada a partir de un blur up).
  2. empty – nessun placeholder; se muestra el espacio vacío hasta que la imagen esté lista.
  3. dominantColor – se extrae el color más predominante de la imagen y se usa como fondo sólido.

Además, puedes proveer tu propia imagen borrosa mediante la propiedad blurDataURL (útil cuando la imagen proviene de un CDN externo y no quieres que Next.js la procese).

Placeholder blur

Este es el más común porque da una vista previa suave de la imagen final. Next.js crea automáticamente una imagen de 20px de ancho, la desenfoca y la incruta como base64 dentro del atributo srcset del elemento <img>.

function ImgBlur({src, alt, width, height}) {
  // Simulamos el HTML que Next.js generaría
  const placeholder = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBxISEhUTExIVFRUVFRUWFRUXFRUYFRUWFRUWFRUWFhUVFRUYHSggGBolGxUVITEhJSkrLi4uFx8zODMsNygtLisBCgoKDg0OGxAQGi0lHyUtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLf/AABEIAKAA0AMBIgACEQEDEQH/xAAcAAABBQEBAQAAAAAAAAAAAAAAAQIDBAUGBwj/xABEEAACAQIEBAYHBAUFAQAAAAABAAIDBBEFEiExBkFREyJhcYGRBxQykaEHobFCMzNUYnKCksEVJHNDY4KT/8QAGgEAAwEBAQEAAAAAAAAAAAAAAAECAwQFBv/EACsRAAICAQQCAwEBAAAAAAAAAAABAhEDIRIxQQUTEyJhcZEUobGx0RQjNC/9oADAMBAAIRAxEAPwD9//Z" ;
  return `<img src='${placeholder}' alt='${alt}' width='${width}' height='${height}' style='display:block;max-width:100%;height:auto;background-color:#f0f0f0;' />`;
}
console.log(ImgBlur({src:'/paisaje.jpg', alt:'Montañas', width:800, height:450}));

Salida esperada (el atributo src contiene la imagen borrosa en base64):

Montañas

Cuando la imagen real se descarga, Next.js reemplaza el src por la URL optimizada y elimina el estilo de fondo.


Placeholder empty

Si prefieres que no se muestre nada mientras se carga (por ejemplo, en una galería donde el layout ya reserva el espacio), basta con establecer placeholder='empty'.

function ImgEmpty({src, alt, width, height}) {
  return `<img src='' alt='${alt}' width='${width}' height='${height}' style='display:block;max-width:100%;height:auto;visibility:hidden;' />`;
}
console.log(ImgEmpty({src:'/icono.png', alt:'Icono', width:100, height:100}));

Salida esperada:

Icono

En la práctica, Next.js mantiene el atributo src vacío hasta que la imagen esté lista, momento en el cual lo llena con la URL optimizada.


Placeholder dominantColor

Este placeholder extrae el color más predominante de la imagen y lo aplica como fondo del elemento. Es útil cuando el diseño se basa en bloques de color y deseas evitar un destello blanco.

function ImgColor({src, alt, width, height, color}) {
  return `<img src='' alt='${alt}' width='${width}' height='${height}' style='display:block;max-width:100%;height:auto;background-color:${color};' />`;
}
console.log(ImgColor({src:'/logo.png', alt:'Logo empresa', width:200, height:80, color:'#2c3e50'}));

Salida esperada:

Logo empresa

Uso básico del componente

En un archivo .jsx o .tsx de Next.js, el componente se importa así:

import Image from 'next/image';

export default function MiPagina() {
  return (
    <div>
      <Image
        src='/foto-perfil.jpg'
        alt='Foto de perfil'
        width={400}
        height={400}
        placeholder='blur'
      />
    </div>
  );
}

Las propiedades width y height son obligatorias (excepto cuando usas loader personalizado o fill). Sirven para calcular la relación de aspecto y evitar el layout shift.

Propiedades más usadas

Propiedad Descripción Valor típico
src URL de la imagen (puede ser estática o externa) '/img.jpg' o 'https://ejemplo.com/foto.png'
alt Texto alternativo para accesibilidad Descripción concisa
width Ancho en píxeles (se usa para generar srcset) 800
height Alto en píxeles 600
loader Función personalizada que genera la URL de la imagen ( {src, width, quality} => //mi-cdn.com/${src}?w=${width}&q=${quality} )
priority Forzar carga inmediata (evita lazy loading) true/false
placeholder Tipo de placeholder ('blur', 'empty', 'dominantColor') 'blur'
blurDataURL Imagen borrosa personalizada en base64 (solo con placeholder='blur') 'data:image/jpeg;base64,....'
quality Nivel de compresión (1-100) 75
sizes Anchos relativos para diferentes viewport (como en <img sizes>) '(max-width: 768px) 100vw, 33vw'
style / className Estilos CSS adicionales { borderRadius: '8px' }

Ejemplos completos con salida HTML esperada

A continuación verás varios escenarios reales. Cada bloque de código contiene una función que devuelve el HTML que Next.js generaría (simplificado) y su salida esperada.

1. Imagen estática con blur y tamaños responsive

function ImgResponsiveBlur({src, alt}) {
  // Simulamos srcset con tres ancho: 400, 800, 1200
  const srcset = `
    ${src} 400w,
    ${src} 800w,
    ${src} 1200w
  `;
  const placeholder = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBxISEhUTExIVFRUVFRUWFRUXFRUYFRUWFRUWFRUWFhUVFRUYHSggGBolGxUVITEhJSkrLi4uFx8zODMsNygtLisBCgoKDg0OGxAQGi0lHyUtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLf/AABEIAKAA0AMBIgACEQEDEQH/xAAcAAABBQEBAQAAAAAAAAAAAAAAAQIDBAUGBwj/xABEEAACAQIEBAYHBAUFAQAAAAABAAIDBBEFEiExBkFREyJhcYGRBxQykaEHobFCMzNUYnKCksEVJHNDY4KT/8QAGgEAAwEBAQEAAAAAAAAAAAAAAAECAwQFBv/EACsRAAICAQQCAwEBAAAAAAAAAAABAhEDIRIxQQUTEyJhcZEUobGx0RQjNC/9oADAMBAAIRAxEAPwD9//Z" ;
  return `<img src='${placeholder}' alt='${alt}' srcset='${srcset}' sizes='(max-width: 600px) 100vw, (max-width: 900px) 50vw, 33vw' style='display:block;max-width:100%;height:auto;' />`;
}
console.log(ImgResponsiveBlur({src:'/producto.jpg', alt:'Producto destacado'}));

Salida esperada:

Producto destacado

2. Imagen externa con loader personalizado y dominantColor

Supongamos que tus imágenes están en un CDN propio y quieres aplicar el color dominante como placeholder.

function ImgExternoCDN({src, alt, width, height, dominant}) {
  // Loader que agrega ancho y calidad al URL
  const url = `https://mi-cdn.com/${src}?w=${width}&q=80`;
  return `<img src='' alt='${alt}' width='${width}' height='${height}' style='display:block;max-width:100%;height:auto;background-color:${dominant};' />`;
}
console.log(ImgExternoCDN({src:'fotos/viaje.jpg', alt:'Atardecer en la playa', width:1600, height:900, dominant:'#ff6f61'}));

Salida esperada:

Atardecer en la playa

Cuando la imagen se carga, Next.js remplazará el src vacío por la URL generada por el loader.

3. Imagen de prioridad alta (hero) sin placeholder

function ImgHero({src, alt, width, height}) {
  return `<img src='${src}' alt='${alt}' width='${width}' height='${height}' loading='eager' style='display:block;max-width:100%;height:auto;' />`;
}
console.log(ImgHero({src:'/hero.jpg', alt:'Titular principal', width:1920, height:1080}));

Salida esperada:

Titular principal

Configuración avanzada en next.config.js

Para afinar el comportamiento global de next/image puedes editar el archivo next.config.js. Algunas opciones útiles:

  • domains: lista de dominios externos permitidos para cargar imágenes sin necesidad de un loader personalizado.
  • deviceSizes: arreglo de anchos (en píxeles) que Next.js considerará al generar srcset para dispositivos típicos.
  • imageSizes: arreglo de anchos para imágenes que no son de layout full‑width (por ejemplo, thumbnails).
  • path: permite cambiar el endpoint interno del Image Optimizer (por defecto /_next/image).

Ejemplo de configuración:

/** @type {import('next').NextConfig} */
const nextConfig = {
  images: {
    domains: ['images.unsplash.com', 'cdn.mi-sitio.com'],
    deviceSizes: [640, 768, 1024, 1280, 1600],
    imageSizes: [32, 48, 64, 96],
    path: '/_next/image',
    dangerouslyAllowSVG: true,
    contentDispositionType: 'attachment',
    contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;"
  },
};

export default nextConfig;

Con esta configuración, cualquier imagen cuyo src sea una URL completa que incluya uno de los dominios listados será optimizada automáticamente por el Image Optimizer de Next.js, aplicando los mismos principios de resize, formato y calidad.


Buenas prácticas

  1. Siempre especifica width y height (excepto cuando usas fill o un loader personalizado que maneje el aspecto). Esto evita el layout shift y mejora la puntuación de Core Web Vitals.
  2. Usa priority solo en imágenes por encima del doblez (hero, banners, logos principales). Sobrecargar de priority puede empeorar el rendimiento porque el navegador descarga imágenes que aún no se ven.
  3. Prefiere placeholder='blur' para imágenes de contenido donde la vista previa ayuda a la percepción de velocidad. Evita usarlo en iconos muy pequeños porque el costo de generar la versión borrosa puede superar el beneficio.
  4. Optimiza el tamaño de las imágenes fuente antes de subirlas al repositorio. Next.js no hará milagros si la imagen original pesa 5 MB; lo mejor es comenzar con un archivo razonable (menos de 1 MB) y dejar que el optimizador ajuste la calidad.
  5. Controla los dominios externos en next.config.js. No dejes la lista vacía si vas a cargar imágenes de CDNs conocidos; de lo contrario Next.js bloqueará la petición por seguridad.
  6. Prueba con diferentes conexiones (por ejemplo, usando las herramientas de Chrome DevTools → Network → throttling) para asegurarte de que el lazy loading y los placeholders se comportan como esperas en redes lentas.
  7. Monitorea el uso del Image Optimizer en producción. Si notas un aumento inesperado en el tiempo de respuesta del servidor, revisa si estás generando muchas variantes de tamaños innecesarios; ajusta deviceSizes y imageSizes según tu audiencia.

Errores comunes

  • Olvidar width y height: provoca que el elemento tenga dimensiones 0×0 hasta que la imagen cargue, causando desplazamientos de layout.
  • Usar placeholder='blur' en imágenes SVG: el blur de un SVG no se genera correctamente; mejor usar placeholder='dominantColor' o empty.
  • Cargar imágenes externas sin agregar el dominio a next.config.js: Next.js devuelve un error 400 y la imagen no se muestra.
  • Establecer priority en todas las imágenes: pierde el beneficio del lazy loading y puede afectar el LCP (Largest Contentful Paint).
  • Sobrecargar el quality a valores muy bajos (ej. 10): aunque reduce el peso, la imagen se ve pixelada y afecta la percepción de marca.
  • No limpiar blurDataURL cuando cambias la imagen: si usas un blurDataURL estático y la imagen real cambia, el placeholder puede no coincidir con el tono de la nueva imagen, provocando un salto visual desagradable.

Conclusión

El componente next/image es una herramienta poderosa que encapsula en una sola API varias técnicas de optimización de imágenes que, de otro modo, requerirían configuraciones complejas y herramientas externas. Gracias a su optimización automática de tamaños y formatos, su lazy loading incorporado y sus placeholders flexibles, puedes mejorar significativamente el rendimiento de tus aplicaciones Next.js sin sacrificar la calidad visual.

En este artículo viste:

  • cómo funciona el Image Optimizer detrás de escena,
  • qué significa el lazy loading y cómo forzar la carga inmediata cuando es necesario,
  • cuáles son los tipos de placeholders disponibles y cuándo cada uno resulta útil,
  • ejemplos prácticos con el HTML que Next.js genera y su salida esperada,
  • y finalmente, una lista de buenas prácticas y errores típicos para que puedas aplicar lo aprendido de forma segura en tus proyectos.

Con esta información, estás listo para revisar tu código existente, añadir width y height donde falten, elegir el placeholder adecuado para cada caso y ajustar la configuración de next.config.js según el origen de tus imágenes. Así podrás ofrecer una experiencia más rápida, estable y agradable a tus usuarios.


Próximos pasos

Si deseas seguir profundizando, te recomiendo:

  • Leer la documentación oficial de Next.js sobre Image Optimization (https://nextjs.org/docs/api-reference/next/image).
  • Experimentar con un loader personalizado que se integre con un servicio de transformación de imágenes como Cloudinary o Imgix.
  • Auditar las imágenes de tu proyecto con herramientas como Lighthouse o Web Vitals para medir el impacto de los cambios realizados.
  • Explorar el uso del componente next/future/image (experimental) que trae mejoras en el manejo de srcset y sizes.

¡Manos a la obra y que tus imágenes carguen siempre a la velocidad de la luz!

Comentarios (0)

Sé el primero en comentar.

Dejá tu comentario