The problem: a 12 MB photo on a phone
Snip Bio creators upload a profile photo and a cover image, often straight from a phone camera: 12 MB, 4,000 pixels wide, with GPS coordinates in its metadata. Visitors view profiles on phones over mobile data, where the avatar is 96 pixels across. Serving the original would mean downloading 12 MB to show an image the size of a thumbnail, and publishing where the creator lives.
What one upload becomes
- Sizes: an avatar at 96, 192 and 384 pixels; a cover at 640, 1,280 and 2,560. Larger sizes cover high-density screens.
- Formats: modern formats like WebP and AVIF are much smaller than JPEG for the same quality, for browsers that support them, with JPEG as the fallback.
- Stripped metadata: remove EXIF data (camera, time, GPS location) from every served copy.
- Fixed orientation: apply the phone's rotation flag, so images don't appear sideways.
The original stays in object storage (09.04 How object storage works), untouched, so variants can always be regenerated.
Make variants up front, or on demand?
At upload: a job (07.05 A job queue on Postgres with SKIP LOCKED) creates every variant and stores them. Reads are plain cached files. But every upload pays for sizes that may never be requested, and adding a new size means reprocessing every image.
On demand at the edge: the image URL carries the transformation (/img/avatar/4821.jpg?width=192&format=auto), and
an image service at the CDN fetches the original, transforms it, and caches the result. Cloudflare, for example,
describes its transformations as optimising and caching remote images from any origin at the edge: on a miss it fetches
the original, applies the requested parameters such as width, format and quality, and caches the result. Nothing is
generated until someone asks for it.
Snip does both: avatar sizes are generated at upload (they're on every profile view), and covers are transformed on demand.
Don't let parameters explode the cache
On-demand transforms put parameters in the URL, so every distinct parameter set is a separate cache entry and a
separate, CPU-heavy transform (06.07 CDNs in depth). Left open, width=191, width=192, width=193 … are all different,
and anyone can make the service resize images all day. So:
- Allow a fixed set of sizes (snap requests to the nearest allowed width) and qualities.
- Sign transformation URLs, or only accept them from your own pages, so others can't generate arbitrary work.
- Cache the transformed result for a long time: originals don't change in place, and a new upload gets a new key.
Let the browser pick
The page offers the sizes and lets the browser choose, based on how large the image will appear and the screen's pixel density:
<img
src="/img/cover/4821.jpg?width=1280"
srcset="/img/cover/4821.jpg?width=640 640w,
/img/cover/4821.jpg?width=1280 1280w,
/img/cover/4821.jpg?width=2560 2560w"
sizes="(max-width: 600px) 100vw, 600px"
alt="Cover image"
width="1280" height="480" />srcset lists the candidates with their real widths; sizes says how wide the image will be on screen. A phone 375
pixels wide at 3× density needs about 1,125 pixels, so it picks the 1,280 version, not the 2,560. The width and
height attributes reserve space so the page doesn't jump as images load.
Seen in production
Cloudflare
2026Image transformations
Cloudflare's transformations optimise and manipulate images stored outside its own image service, caching remote images from any origin at the edge. On a request, it fetches the original from the origin, applies the requested parameters such as format, width and quality, and caches the transformed result. By default, it only accepts source images from the same zone the transformations are served on.
What we foundTransform originals on request at the edge and cache the result; nothing is generated until it's asked for.
Transform images (Cloudflare documentation)developers.cloudflare.com ↗Mozilla
2025Responsive images
srcset defines the set of images the browser may choose between and each image's intrinsic width (with the w
unit), and sizes gives media conditions with the size the image will be displayed at. The browser takes into
account screen size, pixel density, zoom, orientation and network speed, and picks the first image larger than the
slot it will fill.
What we foundOffer several widths with srcset and the display size with sizes; the browser picks the right file.
Using responsive images in HTML (MDN Web Docs)developer.mozilla.org ↗Snip's on-demand image service accepts any width in the URL. A scraper requests every width from 1 to 4,000 for thousands of profiles. What happens, and what's the fix?
Recap
Interview questions
Design image upload and delivery for a social app.