Lo que quizá no sabes de Next Image

Si has trabajado con Next.js, lo más probable es que te hayas cruzado con el componente Next Image. Esta solución de optimización de imágenes sin complicaciones no solo es compatible con formatos modernos como webp y avif, sino que además genera varias versiones adaptadas a distintos tamaños de pantalla.
Para aprovechar esta magia, basta con añadir el siguiente código a tu página:
import Image from 'next/image'; export default function Page() { return ( <Image src="/profile.png" width={500} height={500} alt="Picture of the author" /> );}
Pero, como pasa con cualquier truco de magia, detrás hay mucho trabajo que hace que todo funcione sin fricciones. En este artículo vamos a ver cómo funciona Next Image y a aclarar algunas ideas equivocadas muy extendidas sobre él.
Arquitectura principal
La arquitectura de next/image se apoya principalmente en tres piezas:
- El componente de React Next Image
- La Image API
- El optimizador de imágenes
Componente de React
La función principal del componente es generar el HTML de imagen correcto a partir de las propiedades recibidas y construir las distintas URL que se colocan en los atributos srcset y src. Este es un ejemplo de lo que devuelve el componente Next Image:
<img alt="Example" loading="lazy" width="500" height="500" decoding="async" data-nimg="1" style="color:transparent" srcset="/_next/image?url=%2Fimages%2Fexample.jpg&w=640&q=75 1x, /_next/image?url=%2Fimages%2Fexample.jpg&w=1080&q=75 2x" src="/_next/image?url=%2Fimages%2Fexample.jpg&w=1080&q=75">
Veamos con más detalle la URL generada:
/_next/image?url=/images/example.jpg&w=640&q=75
Esta URL codificada acepta dos parámetros, w (ancho) y q (calidad), que se ven mejor en la versión decodificada. Fíjate en que no hay parámetro h (alto); de eso hablaremos más adelante.
Image API
La Image API de Next funciona como un proxy de imágenes, parecido a IPX. Se encarga de lo siguiente:
- Recibe una URL de imagen, un ancho y una calidad
- Valida los parámetros
- Determina las políticas de cache
- Procesa la imagen
- Sirve la imagen en un formato compatible con el navegador del usuario
Ahora que todo empieza a encajar, repasemos brevemente la última pieza del puzle antes de sacar conclusiones.
Optimizador de imágenes
Next Image utiliza distintas librerías de optimización de imágenes, Sharp o Squoosh, según ciertas condiciones:
Sharp es un módulo de Node.js rápido y eficiente para optimizar imágenes que se apoya en la librería nativa libvips.
Squoosh es una solución de optimización basada íntegramente en Node. Es más lenta, pero no necesita instalar librerías adicionales en la máquina. Por eso se recomienda Sharp en producción, mientras que en entornos locales se usa Squoosh por defecto.
Recomendamos usar Sharp también en local. Aunque Sharp y Squoosh optimizan las imágenes de forma bastante parecida, los algoritmos de compresión de Sharp pueden degradar ligeramente los colores en comparación con Squoosh. Esto puede provocar diferencias visuales entre producción y local, sobre todo cuando intentas que el color de fondo de una imagen coincida con el fondo de la página.
Conclusiones prácticas
Una vez entendida la arquitectura de next/image, podemos desmontar algunos mitos habituales y sacar ideas para usarlo mejor.
next/image no recorta
Un error muy común entre desarrolladores es pensar que next/image recorta las imágenes. La confusión viene de que puedes pasarle al componente las propiedades width, height y fill, lo que da la impresión de que la imagen se ha recortado. En realidad no es así. El componente Next Image necesita el ancho y el alto sobre todo para asignarlos a la etiqueta img y evitar saltos de maquetación (layout shifts).
Como ya hemos visto, la Image API no acepta un parámetro de alto, así que hoy por hoy no es posible cambiar la relación de aspecto de la imagen original. Si no usas la propiedad fill, cuando el ancho y el alto no coincidan la imagen simplemente se estirará o se encogerá.
Eso sí, si usas TailwindCSS el comportamiento es distinto por su regla CSS global por defecto:
img,video { max-width: 100%; height: auto;}
Esto hace que los problemas de layout shift sean más difíciles de detectar.
Ancho mostrado ≠ ancho cargado
Otro posible foco de confusión es que la propiedad width que pasas a next/image no es el ancho real al que se redimensiona la imagen. Como vimos en el ejemplo del principio, pasar width={500} al componente hace que la imagen se redimensione a 640px de ancho, tal y como se ve en la URL generada:
/_next/image?url=/images/example.jpg&w=640&q=75
Si esperas que la versión retina x2 use una imagen de 1000px o 1280px de ancho, te llevarás una sorpresa: el ancho real será de 1080px. Es normal que te preguntes de dónde salen estos números.
Next.js redimensiona las imágenes al tamaño más cercano de los arrays deviceSizes e imageSizes, que puedes definir en next.config.js. Por defecto son estos:
module.exports = { images: { deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840], imageSizes: [16, 32, 48, 64, 96, 128, 256, 384], },};
Lo importante aquí es que la configuración por defecto puede perjudicar al rendimiento y bajar tu puntuación en PageSpeed Insights de Lighthouse. Se nota especialmente cuando muestras imágenes grandes en una página. Por ejemplo, si quieres renderizar una imagen de 1250px de ancho, la imagen que se cargará tendrá 1920px. La diferencia entre el tamaño necesario y el cargado es aún mayor en las versiones retina x2, que se redimensionan a 3840px. Por suerte, puedes solucionarlo añadiendo más tamaños a los arrays deviceSizes o imageSizes (documentación).
Puedes optimizar imágenes sin el componente next/image
Conociendo la arquitectura principal, es fácil ver que puedes usar la Image API sin pasar por next/image. Hay varios casos en los que esto resulta útil.
Primero, puedes renderizar imágenes optimizadas dentro de un canvas. Tanto si cargas las imágenes en el canvas desde fuentes externas como desde almacenamiento local, basta con pasar la URL correcta a la API y funcionará sin problemas.
Además, puedes usarla para optimizar las imágenes OG o para crear tu propio componente basado en la etiqueta <picture> y tener un mejor control de la dirección de arte.
La Image API está en la ruta /_next/image y acepta solo tres parámetros adicionales: URL, ancho (w) y calidad (q).
/_next/image?url=https://example.com/test.jpg&w=640&q=75
Recuerda que la API comprueba el parámetro de ancho y que solo puede ser un número incluido en la configuración de deviceSizes o imageSizes.
Usa import para las imágenes locales
Con next/image hay dos formas de cargar imágenes locales:
import Image from 'next/image';import profileImg from './profile.jpg';export default function Page() { return ( <> {/* Using absolute path */} <Image src="/profile.png" width={500} height={500} alt="Picture of the author" /> {/* Using imported image via relative path */} <Image src={profileImg} alt="Picture of the author" /> </> );}
Usar una ruta absoluta es habitual con imágenes locales en ejemplos, tutoriales e incluso proyectos open source. Es fácil pensar que no hay ninguna diferencia importante, más allá de la asignación automática de ancho y alto. Pero sí la hay. Cuando accedes a las imágenes de la carpeta public mediante una ruta absoluta, Next.js respeta las políticas de cache del servidor de destino, lo que por defecto supone una cache de 30 días en lugar de public,max-age=31536000,immutable. Una política de cache de 30 días para las imágenes puede bajar bastante tu puntuación en Lighthouse.
Cómo funciona sizes y el truco de 100vw
El componente next/image acepta una propiedad llamada 'sizes', equivalente al atributo sizes de la etiqueta img de HTML. Pero, como en otros aspectos que hemos visto, también hace algunas operaciones propias. El atributo 'sizes' trabaja junto con 'srcset' y acepta una lista de condiciones del navegador y los anchos de imagen que deben aplicarse en cada caso. Si no lo conoces, te recomendamos echar un vistazo a esta documentación y a este ejemplo en codesandbox. Este es un ejemplo de imagen que usa 'sizes':
<img srcset="/img/html/vangogh-sm.jpg 120w, /img/html/vangogh.jpg 193w, /img/html/vangogh-lg.jpg 278w" sizes="(max-width: 710px) 120px, (max-width: 991px) 193px, 278px">
Entremos en detalle para entenderlo mejor. Cuando usas Next Image sin indicar la propiedad 'sizes', tu 'srcset' incluirá dos URL: una para la versión estándar (x1) y otra para la versión Retina (x2). Con esta configuración, el navegador siempre elegirá la versión Retina en un dispositivo Retina. Esto se debe a que el 'srcset' usa la sintaxis 1x y 2x.
<img srcset=" /_next/image?url=%2Fimages%2Fexample.jpg&w=640&q=75 1x, /_next/image?url=%2Fimages%2Fexample.jpg&w=1080&q=75 2x "/>
Básicamente, el navegador lo interpreta así: "Carga esta URL para una densidad de píxeles 2x y esta otra para una densidad 1x". Por tanto, si tu diseño muestra la imagen más pequeña en escritorio que en móvil o tablet, con la sintaxis por defecto de Next Image el navegador cargará siempre la versión grande. Por desgracia, esto puede empeorar el rendimiento y bajar la puntuación en Lighthouse.
Sin embargo, hay una forma de indicarle al navegador que cargue las imágenes según el ancho adecuado. En lugar de añadir los parámetros 1x y 2x a las URL del 'srcset', indicas el ancho de la imagen. Fíjate en estas instrucciones para el navegador:
<img srcset=" /_next/image?url=%2Fimages%2Fexample.jpg&w=640&q=75 640w, /_next/image?url=%2Fimages%2Fexample.jpg&w=1080&q=75 1080w "/>
En este caso, el navegador elige la imagen más adecuada para el tamaño con el que se muestra en la página. Si una imagen en móvil ocupa 600px de ancho (1200px en Retina), elegirá la versión 1080w. En cambio, si en escritorio solo ocupa 300px (600px en Retina), el navegador optará por 640w.
La ventaja de este enfoque es que se cargan las imágenes que mejor encajan con el tamaño de pantalla, lo que mejora el rendimiento al reducir su peso. Ahora que conocemos las ventajas, podemos aplicar esta estrategia en Next Image con el truco de 100vw. Aunque no puedes decirle directamente a Next Image que use parámetros de ancho (w) junto a la URL en lugar de las opciones de densidad de píxeles (1x**), sí puedes recurrir a un apaño que se deriva de cómo está programado Next Image:
- Si tu atributo 'sizes' contiene valores en
vw, solo conservará los tamaños mayores que eldeviceSizemás pequeño (640 por defecto) multiplicado por el porcentaje (100vw= 1,50vw= 0.5). Si indicas100vw, obtendrás 8 URL. - Si tu propiedad 'sizes' contiene valores que no son
vw, tu 'srcset' incluirá TODOS LOS TAMAÑOS (es decir, todas las combinaciones posibles dedeviceSizeseimageSizes), con un total de 16 URL.
Para verlo claro, examinemos el código generado con 100vw:
<img sizes="100vw" srcset=" /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&w=640&q=75 640w, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&w=750&q=75 750w, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&w=828&q=75 828w, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&w=1080&q=75 1080w, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&w=1200&q=75 1200w, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&w=1920&q=75 1920w, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&w=2048&q=75 2048w, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&w=3840&q=75 3840w " src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&w=3840&q=75"/>
Si incluyes un valor en px dentro de 'sizes' (p. ej. (max-width: 1024px) 800px, 300px), la lista de URL crece todavía más y llega a 16 con la configuración por defecto. Lo ideal sería generar 4 URL para una imagen concreta, como hacen otros frameworks, en lugar de inflar el HTML con muchas opciones innecesarias, de las que quizá ninguna tenga el tamaño exacto que necesitas.
Todo esto deja clara una idea: para llenar el 'srcset' con más versiones y mejorar el rendimiento en distintas resoluciones, basta con poner 'sizes' a 100vw. Este truco fuerza la creación de URL para 8 tamaños, empezando por 640px.
Eso sí, como este método puede aumentar mucho el tamaño del HTML, sobre todo si has añadido imageSizes o deviceSizes extra, conviene aplicarlo con cuidado.
Aunque solo podemos especular sobre el motivo exacto de esta arquitectura, suponemos que en proyectos grandes, con imágenes de proporciones distintas usadas en muchos sitios, generar versiones de tamaño medio puede resultar útil. Esas versiones cubrirían la mayoría de los casos y probablemente aprovecharían más la cache, sin perder facilidad de uso.
Conclusiones
Aunque Next Image simplifica la gestión de imágenes y aporta ventajas importantes, le vendrían bien funciones adicionales como el recorte avanzado y el redimensionado preciso que ofrecen soluciones de terceros. También sería útil un componente <picture> específico para afinar la dirección de arte. Echamos especialmente de menos una forma automática de generar cuatro versiones de la imagen a 0.25x, 0.5x, 1x y 2x del ancho indicado.
Aun así, para la mayoría de los casos, la experiencia de desarrollo y la eficiencia de Next Image son más que suficientes.