Image Transformations and the CDN
The idea in one sentence
Section titled “The idea in one sentence”Supabase Storage can resize and reprocess an image on the fly at request time using plain query parameters, so you never have to pre-generate and store a thumbnail, medium, and large copy of every image yourself.
Requesting a transformed image
Section titled “Requesting a transformed image”Instead of uploading one photo.png and separately uploading photo-thumb.png, photo-medium.png, and so on, you upload the image once and ask for whatever size you need when you serve it. Both getPublicUrl and createSignedUrl accept a transform option:
const { data } = supabase.storage .from('avatars') .getPublicUrl('user123/photo.png', { transform: { width: 200, height: 200, resize: 'cover', }, });width and height set the target dimensions in pixels. resize controls how the image fits those dimensions: cover (the default) fills the target size and crops any excess, contain fits the whole image inside the target size while preserving aspect ratio, and fill stretches the image to the exact dimensions without preserving aspect ratio. A quality option is also available to control compression. Requesting three different sizes of the same source file is just three different query strings against the same underlying object:
const thumb = supabase.storage.from('avatars').getPublicUrl('user123/photo.png', { transform: { width: 64, height: 64, resize: 'cover' },});const medium = supabase.storage.from('avatars').getPublicUrl('user123/photo.png', { transform: { width: 400, height: 400, resize: 'contain' },});No new upload happened for either of those — the same stored original serves both.
Why the CDN cache matters here
Section titled “Why the CDN cache matters here”The first request for a given file plus a given set of transform parameters does real work: Storage decodes the original, resizes it, and returns the result. That would be slow if it happened on every single request. It does not, because the transformed output is cached at the CDN edge, keyed by the file and the exact transform parameters used. The next request for the same width/height/resize combination on the same object is served straight from the edge cache, without reprocessing the image again. Change any parameter, though, and it is a new cache key — a fresh variant gets generated and cached on its first request.
Worth noting factually: on hosted Supabase, image transformations are a feature gated to paid plans rather than something available on every project tier, so it is worth checking your plan before relying on it in production.
The tradeoff against pre-generating sizes yourself
Section titled “The tradeoff against pre-generating sizes yourself”The alternative — resizing images yourself at upload time and storing a photo-thumb.png, photo-medium.png, and photo-large.png — avoids any transform cost at read time, but it comes with real costs of its own: you store N copies of every image forever, you have to pick your fixed sizes up front, and if you ever need a size you did not anticipate you have to reprocess and re-upload everything retroactively. On-the-fly transformation flips that trade: you pay a small amount of latency on the first request for a given size, in exchange for storing exactly one original per image and being able to request any size, at any time, without touching the upload path again.
flowchart LR
original[("Original image: photo.png")]
original --> thumb["?width=64&height=64&resize=cover"]
original --> medium["?width=400&height=400&resize=contain"]
original --> large["?width=1200&height=800&resize=cover"]
thumb --> cdnThumb["Cached at CDN edge after first request"]
medium --> cdnMedium["Cached at CDN edge after first request"]
large --> cdnLarge["Cached at CDN edge after first request"]