📦 E-commerce / Media / Streaming
Watch egress costs accumulate in real-time as images load from AWS S3 and GCS — while Cloudflare R2 stays at $0.00. Live demo with real images and real byte tracking.
Run Panel A to see real egress costs accumulating on your origin. Read the Worker code in the middle. Run Panel B to see the same images served from R2 — $0.00, always.
Same images · egress billed at AWS S3 / GCS rates
Copy the code, configure the route, run one command. Your origin server: zero changes.
Save as worker.ts. Intercepts /images/* — serves from R2, falls back to origin, copies in background.
// worker.js — serve images from R2 instead of your origin
// Route: *yourdomain.com/images/*
// Your origin server: ZERO code changes required
//
// wrangler.toml bindings needed:
// [[r2_buckets]]
// binding = "R2"
// bucket_name = "your-media-bucket"
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url)
// Only intercept image requests — everything else passes to origin unchanged
if (!url.pathname.startsWith('/images/')) {
return fetch(request)
}
// Strip leading slash to get the R2 object key
// e.g. "/images/products/chair.jpg" → "products/chair.jpg"
const key = url.pathname.slice('/images/'.length)
// 1. Check R2 first — zero egress fee, served from nearest Cloudflare PoP
const r2Object = await env.R2.get(key)
if (r2Object !== null) {
const headers = new Headers()
r2Object.writeHttpMetadata(headers) // copies Content-Type from stored metadata
headers.set('Cache-Control', 'public, max-age=86400')
headers.set('X-Served-From', 'cloudflare-r2')
headers.set('X-Egress-Cost', '$0.00')
return new Response(r2Object.body, { headers })
}
// 2. Not in R2 yet — fetch from origin once, return immediately,
// store in R2 in the background so next request is free
const originResponse = await fetch(request)
if (!originResponse.ok) return originResponse
ctx.waitUntil((async () => {
const buffer = await originResponse.clone().arrayBuffer()
await env.R2.put(key, buffer, {
httpMetadata: {
contentType: originResponse.headers.get('content-type') ?? 'image/jpeg',
cacheControl: 'public, max-age=86400',
},
customMetadata: {
'cached-at': new Date().toISOString(),
'origin-url': request.url,
},
})
})())
// Return origin response to user — next request will hit R2
const respHeaders = new Headers(originResponse.headers)
respHeaders.set('X-Served-From', 'origin-first-load')
return new Response(originResponse.body, {
status: originResponse.status,
headers: respHeaders,
})
},
}✅ What stays the same on your server:
AI-generated images served from R2
| Provider | Storage/GB-mo | Egress/GB | CDN required? | 10TB/mo egress | 100TB/mo egress |
|---|---|---|---|---|---|
| AWS S3 | $0.023 | $0.09 | Yes, +$ | ~$900 | ~$9,000 |
| Google Cloud | $0.020 | $0.12 | Yes, +$ | ~$1,200 | ~$12,000 |
| Cloudflare R2 | $0.015 | $0.00 | No — built-in | $0 | $0 |
Productionising this