Crear un pipeline de optimización de imágenes con un CDN
Un pipeline de imágenes combina almacenamiento, transformaciones, marcado responsivo y caché. Un CDN puede reducir el trabajo de entrega, pero las URL de transformación y las reglas de firma son específicas de cada proveedor. Esta guía usa la interfaz de imágenes alojadas de Cloudflare Images y mantiene esos contratos explícitos.
Entender los CDN de imágenes
Los servicios de imágenes pueden crear variantes en distintos tamaños, negociar los formatos compatibles y almacenar en caché los resultados. Verifica qué operaciones ocurren en el momento de la subida o de la entrega, qué formatos son compatibles y cómo interactúan las claves de caché y los controles de acceso. No todos los CDN procesan imágenes sin un servicio de transformación configurado por separado.
Elegir un proveedor de optimización de imágenes
Compara los formatos compatibles, el comportamiento de redimensionado, la entrega privada, los límites operativos y la integración con tu almacenamiento. Los siguientes ejemplos usan variantes predefinidas de Cloudflare Images, no su API independiente de transformación de imágenes remotas. Usa los contratos documentados de URL y firma del proveedor que elijas en lugar de tratar los parámetros de consulta de los CDN de imágenes como intercambiables.
Aprovechar Azure Front Door para la entrega de imágenes
Azure Front Door puede entregar contenido desde un origen de procesamiento de imágenes, pero colocar las imágenes detrás de él no crea por sí solo variantes redimensionadas. Mantén separados el servicio de transformación, la política de acceso al origen y la configuración de caché. Consulta la documentación de Azure Front Door para conocer la configuración de origen y entrega de ese proveedor.
Configurar la optimización de imágenes
Sube una imagen a Cloudflare Images y obtén su hash de cuenta y su ID de imagen. En su panel, crea
variantes predefinidas llamadas w320, w640 y w1280 con los anchos correspondientes, la relación
de aspecto preservada y el ajuste scale-down. Consulta la
documentación de configuración de variantes.
Para este ejemplo, usa una imagen de origen de al menos 1280 píxeles de ancho, para que cada
descriptor de ancho srcset coincida con un ancho de salida real.
Guarda este constructor de URL compartido como images.ts. La configuración de variantes se define
una sola vez y se reutiliza para la entrega responsiva y la firma. Este ejemplo admite
deliberadamente ID de imagen opacos sin segmentos de ruta personalizados.
export const imageVariants = [
{ name: 'w320', width: 320 },
{ name: 'w640', width: 640 },
{ name: 'w1280', width: 1280 },
]
export function getOptimizedImageUrl(accountHash: string, imageId: string, variant: string): string {
const segment = /^[A-Za-z0-9_-]{1,128}$/
if (!segment.test(accountHash) || !segment.test(imageId)) {
throw new Error('Use an account hash and opaque image ID, not a URL or path')
}
if (!imageVariants.some(({ name }) => name === variant)) {
throw new Error('Unknown image variant')
}
return new URL(`https://imagedelivery.net/${accountHash}/${imageId}/${variant}`).href
}
Las URL de entrega de imágenes alojadas llevan el hash de cuenta, el ID de imagen y la variante en
la ruta. Añadir parámetros de consulta width o format arbitrarios a una URL incompleta no es una
API equivalente. Cloudflare puede negociar los formatos de entrega según la solicitud del
navegador; ten presente el
contrato completo de entrega de imágenes alojadas
cuando añadas otro proxy o caché.
Implementar imágenes responsivas
Mantén el componente de React independiente de si las URL son públicas o firmadas. Pásale las
fuentes generadas, las dimensiones intrínsecas y un valor de sizes que coincida con el diseño real:
import type { ReactNode } from 'react'
interface ResponsiveImageProps {
sources: { url: string; width: number }[]
alt: string
width: number
height: number
sizes: string
loading?: 'lazy' | 'eager'
}
export function ResponsiveImage({
sources, alt, width, height, sizes, loading = 'lazy',
}: ResponsiveImageProps): ReactNode {
const fallback = sources[sources.length - 1]
if (!fallback || !Number.isFinite(width) || width <= 0 || !Number.isFinite(height) || height <= 0) {
throw new Error('Supply image sources and positive intrinsic dimensions')
}
return (
<img
src={fallback.url}
srcSet={sources.map((source) => `${source.url} ${source.width}w`).join(', ')}
width={width}
height={height}
alt={alt}
sizes={sizes}
loading={loading}
className="responsive-image"
/>
)
}
Aplica una regla CSS responsiva en la hoja de estilos de tu aplicación:
.responsive-image {
display: block;
max-width: 100%;
height: auto;
}
Para las imágenes públicas, construye la prop sources a partir de la lista compartida de variantes:
import { getOptimizedImageUrl, imageVariants } from './images.ts'
export function publicImageSources(accountHash: string, imageId: string) {
return imageVariants.map(({ name, width }) => ({
width,
url: getOptimizedImageUrl(accountHash, imageId, name),
}))
}
Las props width y height describen la relación de aspecto de la imagen de origen y reservan
espacio en el diseño. Usa carga anticipada para una imagen que probablemente sea el LCP en lugar de
cargar todas las imágenes de forma diferida. Conserva un texto alternativo significativo incluso
cuando falle la entrega; no reintentes indefinidamente una URL de reserva inexistente.
Monitorear el rendimiento
Usa el paquete web-vitals actual y registra LCP, INP y CLS. INP reemplaza a la métrica FID, ya retirada.
Llama una sola vez a este inicializador exclusivo del navegador, después de cumplir la política de
consentimiento de analítica de tu aplicación. Implementa el endpoint /analytics del mismo origen antes
de habilitar la entrega.
import { onCLS, onINP, onLCP, type Metric } from 'web-vitals'
function sendToAnalytics({ name, value, id }: Pick<Metric, 'name' | 'value' | 'id'>): void {
const body = JSON.stringify({ name, value, id })
if (typeof navigator.sendBeacon === 'function' && navigator.sendBeacon('/analytics', body)) return
void fetch('/analytics', { body, method: 'POST', keepalive: true })
.then((response) => {
if (!response.ok) throw new Error('Analytics request failed')
})
.catch(() => console.warn('Could not deliver performance metric'))
}
export function initializePerformanceMonitoring(): void {
onLCP(sendToAnalytics)
onINP(sendToAnalytics)
onCLS(sendToAnalytics)
}
Un beacon en cola no es prueba de recepción en el servidor. Compara las métricas de campo con mediciones controladas en el navegador y con las tasas de solicitudes de imágenes fallidas. No envíes a la analítica URL firmadas completas de imágenes ni contenido de usuario como identificadores de métricas.
Consideraciones de seguridad
La firma ocurre solo en el servidor, después de autenticar a quien hace la llamada y autorizar el acceso a la imagen. Guarda la clave de firma de Images como un secreto del servidor; no es el hash de cuenta ni un token de API, y nunca debe llegar a los bundles del navegador. Las imágenes privadas deben requerir URL firmadas, y sus variantes no deben configurarse para eludir ese requisito.
Guarda este helper exclusivo de Node.js como images.server.ts. Reutiliza el mismo constructor de rutas,
añade el parámetro exp del proveedor y firma la ruta y la consulta completas antes de añadir sig:
import { createHmac } from 'node:crypto'
import { getOptimizedImageUrl } from './images.ts'
export function getSecureImageUrl(
accountHash: string,
imageId: string,
variant: string,
signingKey: string,
expiresIn = 3600,
): string {
if (!signingKey || !Number.isInteger(expiresIn) || expiresIn < 1 || expiresIn > 86400) {
throw new Error('A signing key and bounded expiry are required')
}
const url = new URL(getOptimizedImageUrl(accountHash, imageId, variant))
url.searchParams.set('exp', String(Math.floor(Date.now() / 1000) + expiresIn))
const payload = `${url.pathname}?${url.searchParams.toString()}`
url.searchParams.set('sig', createHmac('sha256', signingKey).update(payload).digest('hex'))
return url.href
}
Genera en el servidor todas las URL de origen responsivas para las imágenes privadas y pasa al componente únicamente esas URL firmadas. Cambiar la variante o la expiración cambia el payload firmado. Sigue la documentación de firma de imágenes privadas y no expongas un firmador público que acepte ID de imagen arbitrarios sin comprobaciones de propiedad. Una firma es una capacidad de portador hasta que expira, no un sustituto de la autorización.
Configura una Content Security Policy a nivel de página que permita el origen de entrega real, como
https://imagedelivery.net. Un comodín para sus subdominios no incluye el nombre de host base. Fusiona esa
directiva en la política completa de la aplicación en lugar de reemplazar directivas no
relacionadas. Habilita únicamente políticas de HSTS que todo el alcance de dominios afectado pueda
soportar sobre HTTPS.
Buenas prácticas
- Deriva las URL responsivas de una única configuración de variantes verificada.
- Haz coincidir
sizescon el diseño, y los descriptores de ancho con los anchos de salida reales. - Conserva la relación de aspecto intrínseca para reducir los cambios de diseño.
- Mantén las claves de firma en el servidor y autoriza el acceso antes de emitir URL privadas.
- Prueba el comportamiento de la caché y la expiración; añadir una firma no convierte en privada una imagen configurada como pública.
- Compara los tamaños de archivo reales, la calidad visual y las métricas de página en lugar de prometer una mejora de velocidad universal.
Conclusión
Un pipeline de imágenes confiable usa los contratos reales de URL y firma del proveedor, marcado responsivo y un comportamiento de entrega observable. Mantén centralizada la configuración compartida de variantes y prueba por separado los flujos públicos y privados. Para procesamiento y entrega gestionados, explora el Smart CDN de Transloadit.
