wandres.dev
IMAGES Y STREAM · medios en el edge

Cloudflare Images: guardar el original y servir la versión que toca

Servir una imagen bien exige guardarla, generar cada tamaño, elegir el formato que entiende cada navegador y cachear el resultado. Images colapsa ese sistema entero en dos piezas: un almacén de originales y una URL cuyo último segmento describe la versión que quieres. Vemos las dos mitades del producto —almacenamiento y motor de optimización—, cómo se sube una imagen y qué identificador la nombra para siempre, cómo se definen variantes con nombre y modos de ajuste, cómo se lee la URL de entrega, y por qué la negociación automática de formato es la optimización que más rinde y que menos código cuesta.

⏱ 18 min

Servir una imagen bien tiene más aristas de las que parece. Hay que guardarla, producir los tamaños que cada hueco de la interfaz necesita, elegir el formato que el navegador del visitante entiende, poner el resultado cerca de él y no rehacer dos veces el mismo trabajo. La respuesta artesanal a ese problema —un bucket, una cola de miniaturas, un servicio de redimensionado, una CDN delante— es un sistema pequeño pero real, con estado, con fallos, con despliegues y con guardias. Cloudflare Images propone colapsarlo en dos piezas: un almacén que guarda el original intacto y una URL cuyo último segmento describe la versión que pides. Todo lo demás —codificar, negociar el formato, cachear— ocurre sin que exista una sola línea de ello en tu repositorio.

🎯 Al terminar esta lección sabrás
  • Distinguir las dos mitades del producto: el almacén de originales y el motor de optimización.
  • Subir una imagen por API o por binding y leer el identificador que la nombra para siempre.
  • Definir variantes con nombre y razonar el modo de ajuste, los metadatos y el acceso público.
  • Leer la URL de entrega como un contrato y entender la negociación automática de formato.

Dos productos bajo un solo nombre

Lo primero que hay que deshacer es una confusión de nomenclatura. Bajo la palabra Images conviven dos cosas separables: un motor de optimización, que transforma imágenes vivan donde vivan, y un almacén de originales, que además las guarda por ti. Puedes usar el primero sin el segundo, y de hecho es lo que hace mucha gente: las imágenes siguen en R2, en tu servidor o en otro proveedor, y Cloudflare solo se ocupa de redimensionarlas y recodificarlas al pasar. El plan gratuito incluye ese motor con una cuota mensual de transformaciones únicas; el plan de pago añade el almacenamiento gestionado.

Cuando eliges el almacén, el gesto de subida es un POST con el fichero o con una URL de la que Cloudflare lo descarga. La respuesta trae lo único que importa recordar: un identificador. Ese id es la clave con la que la imagen existirá desde ese momento en toda tu aplicación, y es lo que guardarás en tu base de datos junto al producto, al usuario o al artículo al que pertenece.

curl --request POST \
  https://api.cloudflare.com/client/v4/accounts/$CUENTA/images/v1 \
  --header "Authorization: Bearer $TOKEN" \
  --form 'file=@./foto.jpg' \
  --form 'metadata={"producto":"a-142"}' \
  --form 'requireSignedURLs=false'

Los metadatos que adjuntas no se comparten nunca con el visitante: viven del lado del servidor, sirven para que tú relaciones la imagen con tu dominio y son recuperables por API. El indicador requireSignedURLs decide si la imagen es pública o exige un token firmado para verse, y esa decisión se puede cambiar después.

Desde un Worker no hace falta ni token ni URL de API: hay un binding con un espacio de nombres dedicado al almacén, y la subida es una llamada de método sobre bytes que ya tienes en la mano —el cuerpo de una petición, un objeto de R2, la respuesta de un fetch—.

const imagen = await env.IMAGES.hosted.upload(request.body, {
  filename: "foto.jpg",
  metadata: { producto: "a-142" },
  creator: usuarioId,
});
return Response.json({ id: imagen.id, variantes: imagen.variants });
ℹ️
Formatos de entrada y de salida

Se aceptan PNG, JPEG, GIF —incluido el animado—, WebP, SVG y HEIC, con AVIF de entrada reservado al plan Enterprise. A la salida se puede servir PNG, JPEG, GIF, WebP, AVIF y SVG. El vectorial es la excepción del catálogo: un SVG se entrega tal cual y los parámetros de redimensionado se ignoran, porque escalar ya es lo que sabe hacer por sí mismo.

Variantes: el catálogo de tamaños

Una imagen almacenada no se sirve directamente: se sirve a través de una variante, que es una receta con nombre. En vez de repartir por la aplicación cadenas con anchos y modos de recorte, defines una vez que miniatura significa ciento cincuenta píxeles ajustados por dentro sin metadatos, y a partir de ahí la plantilla solo escribe la palabra. Puedes tener hasta cien variantes por cuenta, y por defecto existe una llamada public.

curl "https://api.cloudflare.com/client/v4/accounts/$CUENTA/images/v1/variants" \
  --header "Authorization: Bearer $TOKEN" \
  --header "Content-Type: application/json" \
  --data '{"id":"miniatura","options":{"fit":"scale-down","width":150,"height":150,"metadata":"none"},"neverRequireSignedURLs":true}'

El parámetro decisivo es el modo de ajuste, porque decide qué se sacrifica cuando la proporción de la imagen no coincide con la del hueco: la nitidez, el encuadre o el espacio en blanco.

Modo Qué hace Cuándo lo quieres
scale-down Reduce hasta caber dentro del área. Nunca amplía Fotos de tamaño impredecible. Es el ajuste seguro por defecto
contain Encaja dentro del área respetando la proporción, ampliando si hace falta Logotipos y piezas que deben ocupar el máximo posible
cover Rellena el área exacta recortando lo que sobra Rejillas y cabeceras donde la caja manda sobre el contenido
crop Recorta como cover, pero sin ampliar nunca Rejillas con originales de calidad desigual
pad Encaja dentro y rellena el resto con color de fondo Catálogos donde todas las fichas deben medir lo mismo

Dos ajustes menores tienen consecuencias grandes. El tratamiento de metadatos —conservarlos todos, conservar solo el aviso de derechos o eliminarlos— es a la vez una palanca de peso y una decisión de privacidad: una foto de móvil arrastra coordenadas de dónde se tomó, y publicarlas sin querer es una fuga silenciosa. Y la opción de acceso siempre público marca una variante como visible aunque la imagen sea privada, que es exactamente lo que quieres para servir una miniatura borrosa a quien no ha pagado por el original.

💡
Variantes flexibles: comodidad con puerta abierta

Existe un modo que permite pasar parámetros arbitrarios en la URL en vez de un nombre de variante. Es cómodo mientras diseñas, pero viene apagado por defecto y con razón: cualquiera puede pedir combinaciones que tú nunca previste, y cada combinación distinta es una transformación única que se factura. Con variantes con nombre, el conjunto de imágenes que tu cuenta puede generar es finito y lo decides tú.

La URL de entrega y la negociación de formato

Una vez subida la imagen y definida la variante, servirla es componer tres piezas: el hash de tu cuenta, el identificador de la imagen y el nombre de la variante. Esa cadena es todo el contrato.

<img
  src="https://imagedelivery.net/HASH_DE_CUENTA/ID_DE_IMAGEN/miniatura"
  width="150"
  height="150"
  alt="Foto del producto"
/>

El dominio compartido funciona sin configurar nada, pero conviene servir desde tu propio dominio en cuanto el proyecto es serio: evita una conexión más a un tercero, mantiene las imágenes bajo tus reglas de caché y deja de anunciar en el código fuente qué proveedor usas.

Lo que ocurre por debajo es la optimización que más rinde y menos código cuesta. Cuando el navegador pide esa URL manda una cabecera Accept declarando qué formatos entiende, y el motor responde con el más eficiente de los que soporta: AVIF si puede, WebP si no, JPEG como último recurso. Para las imágenes alojadas ese comportamiento es el que viene de fábrica. Una misma dirección devuelve bytes distintos a visitantes distintos, y el ahorro típico frente a un JPEG bien comprimido no es marginal: es la diferencia entre una página que carga y una que se arrastra en una conexión móvil.

Hay una decisión de codificación que conviene conocer porque explica resultados que parecen inconsistentes. Comprimir en AVIF puede ser un orden de magnitud más lento que hacerlo en WebP, así que cuando una imagen es demasiado grande para codificarse a tiempo, el motor renuncia al formato más eficiente y entrega uno más rápido de producir. No es un fallo: es la plataforma resolviendo el compromiso entre el tiempo de generar la imagen y el tiempo de mandarla, que es exactamente el compromiso que tú tendrías que arbitrar a mano en una tubería propia.

La otra pieza de la URL de entrega es el control de acceso. Una imagen marcada como privada solo se sirve con un componente firmado en la dirección, y ese token caduca; sin él, la respuesta es un error en vez de la foto. Combinado con la opción de acceso siempre público a nivel de variante, obtienes el patrón clásico del contenido de pago sin escribir una línea de lógica: la miniatura pequeña se ve siempre, el original en alta resolución exige firma.

flowchart LR
Nav[navegador con cabecera accept] --> Edge[edge mas cercano]
Edge -->|copia presente| Cache[variante ya codificada]
Edge -->|copia ausente| Orig[original almacenado]
Orig --> Motor[motor de optimizacion]
Motor --> Cache
Cache --> Nav
🗄️

El original es sagrado

Se guarda una sola vez y no se toca. Cada variante es una derivación calculada, así que cambiar de diseño no exige volver a subir nada.

🏷️

Variantes con nombre

La plantilla escribe miniatura, no un ancho. El vocabulario visual del producto vive en un sitio y se cambia en un sitio.

🤝

Formato negociado

Una URL, bytes distintos según lo que el navegador declare aceptar. La optimización con mejor relación entre esfuerzo y ahorro.

💳

Tres contadores

Transformaciones únicas, imágenes almacenadas e imágenes entregadas. Ninguno de ellos es transferencia por gigabyte.

Lo que se factura, y por qué importa al diseñar

El modelo de coste tiene tres ejes y ninguno es el que arrastras de la nube clásica. Las transformaciones únicas cuentan cada combinación distinta de original y parámetros, una sola vez por mes natural: pedir la misma receta un millón de veces sigue siendo una transformación. Las imágenes almacenadas se cuentan por original guardado, no por variante, así que definir veinte recetas no multiplica nada. Y las imágenes entregadas cuentan peticiones servidas del almacén, no gigabytes.

Ese detalle de la unicidad mensual es el que debe gobernar tu diseño. Un catálogo con cinco variantes fijas y dos mil originales consume diez mil transformaciones al mes y se cachea entero; el mismo catálogo con anchos calculados a partir del ancho de la ventana del visitante puede generar cientos de recetas por imagen, arruinar la tasa de acierto de la caché y disparar el contador. La regla práctica es corta: pocas recetas, elegidas a mano, redondeadas a un puñado de anchos.

Contador Qué mide exactamente Qué variable tuya lo dispara
Transformaciones únicas Combinaciones distintas de original y parámetros por mes La cardinalidad de recetas, no el tráfico
Imágenes almacenadas Originales guardados en el almacén gestionado El tamaño del catálogo, no el de los ficheros
Imágenes entregadas Peticiones servidas desde el almacén La popularidad de las páginas que las muestran

Merece la pena subrayar una asimetría que ordena la elección entre las dos mitades del producto. Si la imagen vive fuera y solo usas el motor, pagas transformaciones y nada más. Si la imagen vive en el almacén y la sirves por su URL de entrega, pagas almacenamiento y entrega, pero esa entrega ya no cuenta como transformación. Son dos regímenes distintos, y elegir mal el régimen cuesta mucho más que elegir mal un parámetro.

La imagen deja de ser un fichero y pasa a ser una función de la petición

Aquí hay un cambio de categoría que se pasa por alto porque el resultado sigue pareciendo una foto. Durante veinte años, la unidad de trabajo del contenido visual fue el fichero: alguien producía hero-1200.jpg, hero-800.jpg y hero-400.jpg, los subía, y la aplicación elegía uno. Eso convertía cada decisión de diseño en una operación de construcción —un lote nuevo de recortes, una migración, un directorio que se llenaba de versiones huérfanas que nadie se atrevía a borrar— y ataba el aspecto del producto al calendario de quien mantenía la tubería. Images invierte la relación: existe un original y existe una descripción, y lo que viaja por el cable se materializa en el momento de pedirlo, a partir de quién lo pide y con qué. La imagen ya no es un objeto almacenado sino el valor devuelto por una función cuyos argumentos son la receta y las capacidades del cliente. Reconocerás la forma, porque es la misma que ya adoptaron el renderizado en el servidor y la respuesta de una API: dejamos de precalcular todas las combinaciones posibles y empezamos a calcular la que hace falta, apoyados en una caché que hace que el cálculo ocurra una vez y el resultado se reutilice millones. Las consecuencias prácticas son inmediatas y todas van en la misma dirección. Rediseñar la rejilla del catálogo deja de ser una migración de activos y pasa a ser editar una variante. Adoptar un formato que aún no existe no exige reprocesar el archivo histórico, porque el original sigue ahí y la codificación era siempre derivada. Y el borrado de datos personales se vuelve tratable, porque hay un solo sitio donde vive el byte auténtico en lugar de siete copias esparcidas. Pero la contrapartida es igual de real y hay que aceptarla con los ojos abiertos: cuando el resultado se calcula bajo demanda, la economía deja de seguir al almacenamiento y pasa a seguir a la cardinalidad. Lo que te arruina ya no es guardar mucho, que es barato; es permitir que el espacio de recetas sea infinito, porque cada combinación nueva es un cálculo nuevo y una entrada de caché que nadie volverá a pedir. El ingeniero que interioriza esto deja de preguntarse cuántas imágenes tiene y empieza a preguntarse cuántas formas distintas de pedirlas ha dejado abiertas, que es exactamente la pregunta que gobierna el coste, la latencia del primer visitante y la salud de la caché a la vez.

⚔️ Monta un catálogo con tres recetas y ninguna más
  1. Sube tres imágenes de proporciones muy distintas —vertical, apaisada y cuadrada— y anota los identificadores devueltos.
  2. Define exactamente tres variantes: miniatura, tarjeta y detalle. Elige el modo de ajuste de cada una y justifica por escrito qué sacrificas en cada caso.
  3. Sirve la misma URL desde dos navegadores distintos e inspecciona la cabecera de tipo de contenido de la respuesta. Explica qué cambió y quién lo decidió.
  4. Cuenta cuántas transformaciones únicas consumiría tu catálogo real con tu diseño actual, y compáralo con lo que costaría calcular el ancho a partir del tamaño de la ventana.
  5. Marca una imagen como privada y comprueba que una variante con acceso siempre público sigue viéndose. Describe qué producto se puede construir con esa asimetría.