Mejora el rendimiento de tu web: Optimización de imágenes

Si tus imágenes pesan como un elefante mojado, tu negocio sangra por mil cortes invisibles. Esto no va de plugins mágicos ni de “sube el JPG y ya”. Va de estrategia, cirugía y propósito. Bienvenido al frente.
Acto I — La mentira de la medianía
Dogma reproducido
- “Sube las imágenes en JPG a 80% y activa lazy-loading: suficiente.”
- “El CDN ya optimiza solo. No perdamos tiempo.”
- “El diseño manda, luego ya comprimimos si hace falta.”
Autopsia
- Síntoma visible: homepage con héroe a pantalla completa, carrusel de logos y tres tarjetas de producto. Carga “percibida” lenta en móvil; el scroll tartamudea; CLS salta cuando aparece el héroe.
- Causa raíz: imágenes exportadas desde Figma a 2x sin recorte de área útil; PNG donde bastaba JPG; sin width/height; sin srcset/sizes; logos en PNG con fondo blanco; héroe sin prioridad ni preload; uso de background-image para contenido esencial.
- Cadena de decisiones: “tenemos prisa” → “ya lo arreglaremos” → “pon lazy-loading a todo” → “el CDN hará magia” → deuda creciendo y nadie midiendo.
Evidencia mínima
- Peso total imágenes (home, móvil): 4,6 MB → coste de datos medio en 3G ≈ 0,03 € por visita en mercados caros.
- LCP p75 (móvil): 4,1 s. Objetivo ≤ 2,5 s. Penalización SEO y conversión.
- CLS: 0,19 por no fijar dimensiones. Umbral recomendado ≤ 0,1.
Smells
- Todos los img sin width/height. CLS casi garantizado.
- PNG para fotografías, y logos rasterizados a 2000 px “por si acaso”.
- Un único src para todos los anchos; nada de srcset/sizes; “lazy” hasta para el héroe.
Contraejemplo rápido
- Antes (5 líneas)
HTML
<img src="/img/hero.jpg" alt="Nuevo modelo">
<img src="/img/logo1.png">
<img src="/img/card.jpg">
<img src="/img/card2.jpg">
<img src="/img/card3.jpg">
- Después (5 líneas)
HTML
<link rel="preload" as="image"
imagesrcset="/img/hero-1200.avif 1200w, /img/hero-800.avif 800w" imagesizes="(max-width:768px) 100vw, 50vw">
<img src="/img/hero-1200.avif" srcset="/img/hero-800.avif 800w, /img/hero-1200.avif 1200w" sizes="100vw" width="1200" height="800" fetchpriority="high" alt="Nuevo modelo">
<img src="/img/logo1.svg" width="120" height="40" loading="lazy">
<img src="/img/card-600.webp" srcset="/img/card-400.webp 400w, /img/card-600.webp 600w" sizes="(max-width:600px) 100vw, 600px" loading="lazy" decoding="async">
<!-- LCP baja ~35–55% en móvil, CLS≈0 -->
- Regla de salida: si te has reconocido aquí, pon precio al daño: cada 0,5 s de LCP perdido y cada MB extra es fricción que convierte menos y se posiciona peor. Ya no es estética; son euros.
Acto II — La verdad
Visión
- Una imagen en la web no es un archivo: es una decisión de entrega. Optimizar no es “comprimir”, es hacer llegar la intención visual adecuada con el menor coste de tiempo, datos y CPU. Diseño decide el porqué; la ingeniería orquesta el cómo. La estrategia manda: formato, tamaño, prioridad, momento y canal.
Modelo operativo (cadena de mando)
- Contenido → ¿vector, foto, decorativo, crítico para la tarea?
- Formato → SVG/AVIF/WebP/JPEG/PNG según contenido y compatibilidad.
- Tamaño → crop semántico, múltiples anchos, densidades, srcset/sizes.
- Entrega → preload/fetchpriority, decoding, loading, caché/CDN.
- Presentación → width/height, aspect-ratio, object-fit, image-set en CSS.
- Control → métricas: LCP/CLS/transfer size/hits de caché. Automatiza o muere.
Tácticas de campo
- 1) Elegir formato por intención
- Haz: SVG para logos/íconos; AVIF para fotos/hero; WebP como reserva; PNG solo si necesitas transparencia sin banding o píxeles nítidos.
- No uses: PNG para fotografía; JPG para logos; AVIF con grano extremo en UGC altamente comprimido (artefactos feos).
- 2) Responsive de verdad con srcset/sizes
- Haz: usa descriptores w y un sizes honesto al layout. Declara width/height para CLS≈0.
- No uses: un único 2x gigante “por si acaso”; sizes genérico que siempre devuelve el mayor recurso.
- 3) Prioriza como si fuera una evacuación
- Haz: preload del LCP con imagesrcset/imagesizes; fetchpriority=»high» en el LCP; decoding=»async» en lo demás.
- No uses: loading=»lazy» en el LCP; preloads redundantes sin srcset (derroche).
- 4) Fondo ≠ contenido
- Haz: si es decorativo, background-image con image-set() y media queries.
- No uses: fondos para contenido con significado; pierdes alt, semántica y SEO.
- 5) Automatiza el pipeline
- Haz: genera variantes (ancho/calidad/formato) con sharp, ImageMagick o el CDN; purga EXIF, convierte a sRGB, limita calidad por métrica (SSIM/DSSIM).
- No uses: exportar a mano “a ojo” en cada sprint.
Demostración mínima (10 minutos, reproducible)
- Objetivo: convertir una foto héroe 2400×1600 PNG (1,8 MB) a AVIF/WebP en 3 anchos (800/1200/1600), integrarla con preload, medir mejora.
- Entrada: hero.png.
- Pasos:
- Instala sharp y genera variantes.
JS
import sharp from 'sharp';
const sizes = [800, 1200, 1600];
for (const w of sizes) {
await sharp('hero.png').resize({ width: w }).toFormat('avif', { quality: 45 }).toFile(`hero-${w}.avif`);
await sharp('hero.png').resize({ width: w }).toFormat('webp', { quality: 70 }).toFile(`hero-${w}.webp`);
}
- Integra en HTML (LCP con prioridad, fallback a WebP).
HTML
<link rel="preload" as="image" imagesrcset="/img/hero-800.avif 800w, /img/hero-1200.avif 1200w, /img/hero-1600.avif 1600w" imagesizes="100vw">
<picture>
<source type="image/avif" srcset="/img/hero-800.avif 800w, /img/hero-1200.avif 1200w, /img/hero-1600.avif 1600w" sizes="100vw">
<source type="image/webp" srcset="/img/hero-800.webp 800w, /img/hero-1200.webp 1200w, /img/hero-1600.webp 1600w" sizes="100vw">
<img src="/img/hero-1200.webp" width="1200" height="800" alt="Colección Otoño" fetchpriority="high">
</picture>
- Fija el espacio y usa image-set solo en decorativos.
CSS
/* CLS a cero con dimensiones y relación */
.hero { aspect-ratio: 3 / 2; }
.banner--decorativo {
background-image:
image-set(url('/img/bg-1x.avif') 1x,
url('/img/bg-2x.avif') 2x);
background-size:
cover; }
- Salida esperada: peso del héroe ≈ 140–260 KB (según ancho), LCP p75 baja ~30–50% en móvil, CLS ≈ 0 por width/height y aspect-ratio.
Trade-offs
- Más ficheros y rutas (varias variantes) → compensa con automatización y CDN.
- Compatibilidad AVIF en navegadores antiguos → usa picture con fallback WebP/JPEG.
- Preload mal usado puede derrochar ancho de banda → restringe al LCP y declara imagesizes.
Impacto en negocio
- Time-to-change: cambiar una imagen global tarda minutos, no horas, al estar automatizado.
- Coherencia de marca: nitidez/recorte consistentes; nada de logos borrosos ni fondos lavados.
- Rendimiento/retención: menos rebote en móvil, mejor Core Web Vitals, mejor SEO y conversión.
Acto III — El manifiesto
Principios no negociables
- Decide el propósito antes del formato.
- SVG para identidad, AVIF/WebP para foto, PNG solo si está justificado.
- Todo img lleva width y height o aspect-ratio.
- El LCP tiene preload y fetchpriority=»high»; nada de lazy.
- Siempre srcset + sizes con descriptores w, nunca un 2x único.
- Recorta al motivo: no entregues píxeles invisibles.
- Purgar EXIF, normalizar a sRGB y limitar calidad por métrica objetiva.
- Automatiza la generación de variantes en CI/CD.
- Decorativo en CSS, semántico en HTML con alt significativo.
- Mide cada cambio: bytes, LCP, CLS, hit-ratio de caché.
Definición de Hecho (DoD)
- Imágenes nuevas con srcset/sizes y width/height declarados.
- Formato acorde a contenido (SVG/AVIF/WebP/JPEG/PNG) con fallback si procede.
- Héroe y above the fold con preload + fetchpriority.
- Decorativos sin texto embebido; alt vacíos si no aportan significado; alt descriptivo si lo tienen.
- Pipeline genera ≥3 anchos y elimina metadatos; commit incluye script/config.
- Se adjuntan métricas: tamaño transferido y efecto en LCP/CLS.
Métricas de guardarraíl
- Peso total imágenes/home (móvil) ≤ 900 KB p75.
- LCP p75 (móvil) ≤ 2,5 s.
- CLS ≤ 0,1.
- Hit-ratio CDN ≥ 85% para imágenes.
- Variantes por imagen: al menos 3 anchos reales servidos.
Coto de caza (anti-patrones prohibidos)
- PNG para fotografías “porque se ve mejor”. No entra.
- Imágenes sin width/height. CLS garantizado: rechazado.
- loading=»lazy» en el LCP. No.
- Un único src gigante para todos los anchos. No.
- Texto dentro de imágenes para titulares o CTA. Accesibilidad y SEO a la basura.
Plan de acción de 24 h
- 1 Audita la home: lista de imágenes, uso (crítico/decorativo), formato, tamaño. Métrica: peso total actual.
- 2 Monta script de variantes (sharp/ImageMagick) y genera 800/1200/1600 en AVIF+WebP. Métrica: bytes ahorrados.
- 3 Implementa srcset/sizes y declara width/height en todos los img. Métrica: CLS tras cambio.
- 4 Añade preload + fetchpriority al LCP. Métrica: LCP p75 en laboratorio (Lighthouse) y real si tienes RUM.
- 5 Pasa logos a SVG o WebP nítido; mueve decorativos a CSS con image-set(). Métrica: número de recursos y caché CDN.
Cierre
- Esta no es una guía de filtros y sliders. Es un sistema de decisiones con dientes. Apaga el piloto automático, mide, automatiza y entrega intención, no megabytes. El resto es ruido.