Optimización de imágenes
Sirve imágenes optimizadas y receptivas con next/image -- conversión automática de formato, carga diferida y sugerencias de tamaño.
Receta
Tarjeta de referencia rápida -- lista para copiar y pegar.
import Image from "next/image";
// Imagen local (auto width/height desde la importación)
import heroImage from "@/public/hero.jpg";
<Image src={heroImage} alt="Hero banner" priority />
// Imagen remota (debe especificar width y height)
<Image
src="https://cdn.example.com/photo.jpg"
alt="Product photo"
width={800}
height={600}
/>
// Llenar contenedor (receptiva, sin dimensiones explícitas)
<div className="relative h-64 w-full">
<Image
src="/banner.jpg"
alt="Banner"
fill
className="object-cover"
sizes="100vw"
/>
</div>Cuándo usarlo: Cualquier momento que renderices una imagen. next/image maneja carga diferida, conversión de formato (WebP/AVIF), dimensionamiento receptivo y prevención de CLS fuera de la caja.
Ejemplo trabajado
// app/gallery/page.tsx
import Image from "next/image";
type Photo = {
id: string;
url: string;
alt: string;
width: number;
height: number;
};
async function getPhotos(): Promise<Photo[]> {
const res = await fetch("https://api.example.com/photos", {
next: { revalidate: 3600 },
});
return res.json();
}
export default async function GalleryPage() {
const photos = await getPhotos();
return (
<main className="max-w-6xl mx-auto p-6">
<h1 className="text-3xl font-bold mb-8">Galería</h1>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{photos.map((photo, index) => (
<div key={photo.id} className="relative aspect-[4/3]">
<Image
src={photo.url}
alt={photo.alt}
fill
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
className="object-cover rounded-lg"
priority={index < 3} // prioriza imágenes por encima del pliegue
/>
</div>
))}
</div>
</main>
);
}// app/components/product-card.tsx
import Image from "next/image";
import Link from "next/link";
type Product = {
slug: string;
name: string;
price: number;
imageUrl: string;
};
export function ProductCard({ product }: { product: Product }) {
return (
<Link
href={`/products/${product.slug}`}
className="group block border rounded-lg overflow-hidden"
>
<div className="relative aspect-square bg-gray-50">
<Image
src={product.imageUrl}
alt={product.name}
fill
sizes="(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 25vw"
className="object-contain group-hover:scale-105 transition-transform"
/>
</div>
<div className="p-4">
<h3 className="font-medium">{product.name}</h3>
<p className="text-gray-600">${product.price.toFixed(2)}</p>
</div>
</Link>
);
}// next.config.ts -- configura dominios de imagen remota
import type { NextConfig } from "next";
const config: NextConfig = {
images: {
remotePatterns: [
{
protocol: "https",
hostname: "cdn.example.com",
pathname: "/images/**",
},
{
protocol: "https",
hostname: "*.unsplash.com",
},
],
formats: ["image/avif", "image/webp"], // prefiere AVIF, retroceso a WebP
},
};
export default config;Lo que esto demuestra:
- modo
fillpara imágenes receptivas en un diseño de cuadrícula - La prop
sizesindicando al navegador cuán ancha será la imagen en diferentes tamaños de viewport prioritypara imágenes por encima del pliegue (desactiva carga diferida, añade sugerencia de precarga)- Configuración de patrón de imagen remota en
next.config.ts - Contenedores
aspect-[4/3]yaspect-squarepara prevenir CLS
Análisis profundo
Cómo funciona
next/imagerenderiza una etiqueta<img>con atributossrcsetysizes. El navegador selecciona el mejor tamaño de imagen basado en el viewport y la relación de píxeles del dispositivo.- Las imágenes se optimizan bajo demanda en tiempo de solicitud (no en tiempo de compilación). La versión optimizada se almacena en caché en el servidor y CDN para solicitudes posteriores.
- Por defecto, las imágenes están cargadas de forma diferida -- solo se obtienen cuando entran en el viewport. La prop
prioritydesactiva la carga diferida y añade una<link rel="preload">para imágenes LCP. - La prop
fillhace que la imagen llene su contenedor padre (el padre debe serposition: relative,absolute, ofixed). Usa esto cuando no conozcas las dimensiones de la imagen en tiempo de compilación. - Next.js sirve automáticamente imágenes en formatos modernos (WebP, AVIF) a navegadores que los soportan, con retroceso al formato original.
- Las props
widthyheightestablecen la relación de aspecto intrínseca para prevenir Cumulative Layout Shift (CLS). No establecen el tamaño renderizado -- usa CSS para eso.
Variaciones
Imagen receptiva con puntos de ruptura explícitos:
<Image
src="/hero.jpg"
alt="Hero"
width={1200}
height={600}
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 80vw, 1200px"
className="w-full h-auto"
/>Desenfoque de marcador de posición (imágenes locales):
import heroImg from "@/public/hero.jpg";
<Image
src={heroImg}
alt="Hero"
placeholder="blur" // genera automáticamente blurDataURL para imágenes locales
/>Desenfoque de marcador de posición (imágenes remotas):
<Image
src="https://cdn.example.com/photo.jpg"
alt="Photo"
width={800}
height={600}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,/9j/4AAQ..." // debes proporcionar manualmente
/>Deshabilitando la optimización (SVGs, GIFs animados):
<Image
src="/logo.svg"
alt="Logo"
width={200}
height={50}
unoptimized // sirve el archivo original tal cual
/>Notas de TypeScript
import Image, { type ImageProps } from "next/image";
// Extendiendo props de Image
type AvatarProps = Omit<ImageProps, "alt"> & {
name: string;
};
function Avatar({ name, ...props }: AvatarProps) {
return (
<Image
alt={`Avatar for ${name}`}
className="rounded-full"
{...props}
/>
);
}
// Tipo StaticImageData para imágenes importadas
import type { StaticImageData } from "next/image";
import fallback from "@/public/fallback.jpg";
function getImage(url?: string): string | StaticImageData {
return url ?? fallback;
}Trampas
-
Falta la prop
sizesconfill-- Sinsizes, el navegador asume que la imagen tiene100vwde ancho, descargando un archivo innecesariamente grande. Solución: Siempre proporciona una propsizesque coincida con tu diseño CSS. -
Las imágenes remotas requieren
remotePatterns-- Usar una URL remota sin configurarremotePatternsennext.config.tslanza un error de compilación. Solución: Añade el nombre de host (y opcionalmente patrón de ruta) aimages.remotePatterns. -
fillrequiere un padre posicionado -- Si el elemento padre no tieneposition: relative(oabsolute/fixed), la imagen se sale de su contenedor. Solución: AñadeclassName="relative"al padre. -
priorityen demasiadas imágenes -- Marcar muchas imágenes comopriorityderrota el propósito y ralentiza la página. Solución: Solo usapriorityen la imagen LCP (normalmente 1-2 imágenes por encima del pliegue). -
Width y height no controlan el tamaño renderizado --
width={800} height={600}establece la relación de aspecto intrínseca, no el tamaño mostrado. La imagen puede renderizarse más pequeña o más grande dependiendo de CSS. Solución: Usa clases CSS (className) para controlar las dimensiones renderizadas. -
La codificación AVIF es lenta -- AVIF produce archivos más pequeños pero tarda más en generarse en la primera solicitud. Solución: Acepta la latencia de arranque en frío, o elimina
"image/avif"deimages.formatssi el tiempo de respuesta es crítico.
Alternativas
| Alternativa | Úsalo cuando | No lo uses cuando |
|---|---|---|
next/image | Todas las imágenes en una aplicación Next.js (la opción predeterminada) | Necesitas <img> crudo por una razón específica |
Etiqueta <img> nativa | Imágenes estáticas simples sin necesidades de optimización | Quieres WebP/AVIF automático, carga diferida y srcset |
Elemento <picture> | Necesitas dirección de arte (diferentes cultivos en diferentes tamaños) | El tamaño receptivo solo es suficiente |
| Cloudinary o Imgix | Necesitas transformaciones avanzadas (cultivo, marca de agua, detección facial) | La optimización integrada de Next.js es suficiente |
| SVG en línea | Iconos e ilustraciones que necesitan ser estilizados con CSS | Contenido fotográfico |
Preguntas frecuentes
¿Qué hace next/image que una etiqueta img regular no hace?
- Conversión automática de formato (WebP/AVIF)
- Carga diferida por defecto
- Generación responsiva de
srcsetysizes - Prevención de Cumulative Layout Shift (CLS) a través de width/height o fill
- Optimización bajo demanda y almacenamiento en caché CDN
¿Cuándo debo usar la prop fill versus width y height explícitos?
- Usa
fillcuando no conozcas las dimensiones de la imagen en tiempo de compilación (p. ej., imágenes cargadas por el usuario). La imagen llena su contenedor padre. - Usa
widthyheightexplícitos cuando las dimensiones se conocen. Estos establecen la relación de aspecto intrínseca, no el tamaño renderizado.
¿Qué hace la prop priority?
- Desactiva la carga diferida para la imagen.
- Añade una sugerencia
<link rel="preload">para que el navegador la obtenga inmediatamente. - Úsala solo para la imagen LCP (Largest Contentful Paint), típicamente 1-2 imágenes por encima del pliegue.
Trampa: Establecí width={800} y height={600} pero la imagen se renderiza en un tamaño diferente. ¿Por qué?
width y height establecen la relación de aspecto intrínseca para prevención de CLS, no el tamaño renderizado. Usa clases CSS (className) para controlar las dimensiones de visualización reales.
Trampa: Usé fill pero la imagen se sale de su contenedor. ¿Qué salió mal?
El elemento padre debe tener position: relative, absolute, o fixed. Añade className="relative" al <div> padre.
¿Por qué necesito una prop sizes al usar fill?
Sin sizes, el navegador asume que la imagen tiene 100vw de ancho y descarga un archivo innecesariamente grande. Proporciona sizes para que coincida con tu diseño CSS:
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"¿Cómo configuro dominios de imagen remota?
Añade remotePatterns a next.config.ts:
images: {
remotePatterns: [
{ protocol: "https", hostname: "cdn.example.com", pathname: "/images/**" },
],
}¿Cómo añado un marcador de posición desenfocado para imágenes remotas?
Para imágenes remotas, debes proporcionar blurDataURL manualmente (una imagen diminuta codificada en base64). Las imágenes locales obtienen generación de desenfoque automática con placeholder="blur".
¿Cómo tipeo un componente que extiende ImageProps en TypeScript?
import Image, { type ImageProps } from "next/image";
type AvatarProps = Omit<ImageProps, "alt"> & {
name: string;
};
function Avatar({ name, ...props }: AvatarProps) {
return <Image alt={`Avatar for ${name}`} {...props} />;
}¿Qué es el tipo StaticImageData y cuándo lo uso?
StaticImageData es el tipo devuelto cuando importas un archivo de imagen local. Úsalo cuando una función o prop pueda aceptar tanto una importación local como una cadena de URL:
import type { StaticImageData } from "next/image";
function getImage(url?: string): string | StaticImageData {
return url ?? fallbackImage;
}¿Cuándo debo usar la prop unoptimized?
Usa unoptimized para imágenes que no deben procesarse, como SVGs o GIFs animados. El archivo se sirve tal cual sin conversión de formato o cambio de tamaño.
¿Es AVIF siempre mejor que WebP?
AVIF produce archivos más pequeños pero la codificación es más lenta en la primera solicitud (arranque en frío). Si el tiempo de respuesta es crítico, considera eliminar "image/avif" de images.formats y usar solo WebP.
Relacionado
- Font Optimization -- Optimizando fuentes web junto a imágenes
- Server Components -- Páginas renderizadas en servidor con imágenes optimizadas
- Static vs Dynamic -- Cómo se generan estáticamente páginas pesadas en imágenes
- Streaming -- Carga progresiva de páginas ricas en imágenes