Rebasely Beta: data may be purged

examples/storefront Public

Markdown · 28 lines · 1.5 KiB · at 5c7d155

---id: product-imagestype: conceptowner: justincomponent: storefront-apidescribes:  - src/images.tsreviewed: 2026-10-08---# Product imagesEach product has at most one image. It lives in the `storefront-images` R2 bucket, and the product row keeps its key in `image_key`. The API turns the key into `image_url` on the bucket's custom domain, `IMAGES_URL` in `wrangler.jsonc` (`imageUrls` in `src/images.ts`).## Upload`PUT /admin/products/:id/image` takes the raw bytes with a `content-type` of JPEG, PNG, WebP or AVIF, up to 5 MiB. The key is `products/<id>/<hash>.<ext>`, where the hash is the first 8 bytes of the SHA-256 of the image. Because the key depends on the content:- a new image always gets a new URL, so nothing has to be purged from a cache;- the object can be stored with `cache-control: public, max-age=31536000, immutable`;- uploading the same file twice is harmless.The previous object is left in the bucket. A cart page or a CDN may still hold its URL, and images are small enough that keeping them costs little.## ServingBrowsers load images straight from the bucket's custom domain. R2 answers with the headers stored at upload, and Cloudflare's cache in front of the domain serves repeat requests, so image traffic does not run storefront-api at all ([[0008-images-from-the-bucket-domain]]). Until October 2026 the Worker served them at `GET /images/<key>` ([[0002-serve-images-through-the-api]]); that route is gone.The bucket is public on that domain only, so nothing but product images should be written to it.