Una foto de móvil pesa 4 MB; en una lista de notas se ve a 300 píxeles de ancho y debería pesar 30 KB. Cloudflare Images hace esa conversión al vuelo, sirve el formato que mejor soporta cada navegador (AVIF, WebP) y cachea el resultado. En este capítulo lo conecto con los adjuntos de R2.
Para quién es y qué necesitas
Para quien sirve imágenes de usuarios o de su web y quiere que pesen poco sin guardar diez tamaños de cada una.
Antes de empezar:
- Imágenes en R2 (capítulo anterior) o en cualquier URL.
- Las transformaciones activadas en la zona: Images › Transformations › Enable for zone.
Al terminar tendrás miniaturas en AVIF generadas al vuelo o al subir, con caché, y sabrás cuándo optimizar en el build y no en Cloudflare.
Dos formas de trabajar con Images
flowchart LR
subgraph T["Transformar (lo que más uso)"]
N1[Navegador] -->|/cdn-cgi/image/width=400| CF1[Images]
CF1 -->|original| R2[(R2 u otro origen)]
end
subgraph A["Almacenar en Images"]
S[Subida] --> IMG[(Images)]
IMG -->|variantes| N2[Navegador]
end
| Modo | Dónde está el original | Cuándo |
|---|---|---|
| Transformar | En R2, en tu web o en otro servidor | Ya tienes los archivos; quieres miniaturas y formatos modernos |
| Almacenar en Images | En Cloudflare Images | Quieres que Cloudflare gestione también el almacenamiento, con variantes predefinidas |
En mis proyectos uso casi siempre transformar: los originales se quedan en R2, que ya pago, y solo cobra Images por las transformaciones.
Transformar con la URL
Con las transformaciones activadas, cualquier imagen de tu zona se puede pedir así:
https://mydomain.dev/cdn-cgi/image/width=400,quality=80,format=auto/attachments/photo.jpg| Parámetro | Ejemplo | Qué hace |
|---|---|---|
width / height |
width=400 |
Tamaño máximo |
fit |
fit=cover |
Cómo encajar: scale-down, contain, cover, crop, pad |
format |
format=auto |
AVIF o WebP según lo que acepte el navegador |
quality |
quality=80 |
Compresión (1–100) |
gravity |
gravity=face |
Hacia dónde recortar (auto, face, coordenadas) |
En el HTML, con varios tamaños para que el navegador elija:
<img src="/cdn-cgi/image/width=400,format=auto/attachments/photo.jpg" srcset=" /cdn-cgi/image/width=400,format=auto/attachments/photo.jpg 400w, /cdn-cgi/image/width=800,format=auto/attachments/photo.jpg 800w " sizes="(max-width: 640px) 100vw, 400px" width="400" height="300" loading="lazy" alt="Photo attached to the note"/>Transformar desde el Worker con el binding
Cuando el Worker tiene que decidir (comprobar permisos de un adjunto privado, recortar para un avatar), uso el binding:
{ "images": { "binding": "IMAGES" }}const WIDTHS = new Set([200, 400, 800]);
export async function thumbnail(env: Env, key: string, width: number) { if (!WIDTHS.has(width)) { return new Response("Size not allowed", { status: 400 }); } const original = await env.ATTACHMENTS.get(key); if (!original) return new Response("Not found", { status: 404 });
const result = await env.IMAGES.input(original.body) .transform({ width, fit: "scale-down" }) .output({ format: "image/avif", quality: 75 });
const response = result.response(); response.headers.set("Cache-Control", "public, max-age=86400"); return response;}Generar miniaturas al subir
Para listados con muchas imágenes, prefiero generar las miniaturas una vez, al subir el archivo, con la cola del capítulo de segundo plano, y guardarlas en R2. Así la transformación se paga una vez y no en cada mes en que se pide.
flowchart TB
U[Subida] --> R[(R2: original)]
U --> Q[(Cola)]
Q --> C[Consumidor]
C --> I[Images: 200 y 800 px]
I --> R2[(R2: miniaturas)]
async function createThumbnails(env: Env, key: string) { for (const width of [200, 800]) { const original = await env.ATTACHMENTS.get(key); if (!original) return; const r = await env.IMAGES.input(original.body) .transform({ width }) .output({ format: "image/webp" }); await env.ATTACHMENTS.put(`${key}@${width}.webp`, r.image(), { httpMetadata: { contentType: "image/webp" }, }); }}Sitios estáticos: optimizar en el build
En una web de contenido como este blog no hace falta Images: Astro (y la mayoría de generadores) optimizan las imágenes durante el build con <Image> y las sirven como estáticos gratis. Images compensa cuando las imágenes las suben los usuarios o cambian a menudo.
Cuánto cuesta
Cuánto cuesta al mes
Supuestos: 2.000 fotos subidas al mes, 2 tamaños cada una (4.000 transformaciones únicas)
| Concepto | Importe |
|---|---|
| Transformaciones5.000 únicas al mes incluidas | 0,00 $ |
| Almacenamiento de originales en R2Unos 20 GB más allá de lo gratuito | 0,30 $ |
| Total al mes | 0,30 $ |
Precios consultados el 3 de noviembre de 2026. Pueden cambiar.
| Concepto | Gratuito | Pago |
|---|---|---|
| Transformaciones únicas | 5.000 al mes (después, error 9422; lo ya cacheado sigue funcionando) | 5.000 incluidas; 0,50 $ por cada 1.000 más |
| Imágenes almacenadas en Images | — | 5 $ por cada 100.000 |
| Imágenes entregadas desde Images | — | 1 $ por cada 100.000 |
Una transformación única es una imagen de origen con un conjunto de parámetros; repetirla en el mismo mes cuenta una vez, y format y quality no cuentan como parámetros distintos.
Resumen
- Originales en R2; transformaciones con URL para el frontend y con binding desde el Worker.
- Restringe orígenes y tamaños para no pagar transformaciones que no has pedido.
- Para listados, miniaturas generadas una vez al subir.
- Siguiente capítulo: IA en el borde.
