Rebasely Beta: data may be purged

examples/storefront Public

Markdown · 1.2 KiB · at 82cc041

id: product-images
type: concept
owner: justin
component: storefront-api
describes:
  - src/images.ts
reviewed: 2026-10-01

Product images

Each 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.

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.

Serving

GET /images/<key> reads the object from R2 and returns it with the stored headers and its ETag, or a 304 when If-None-Match matches. Only keys under products/ are served. See [[0002-serve-images-through-the-api]] for why the Worker serves them.