Skip to main content
View as Markdown

Presets & Caching

Two concerns that sit behind the resize and format parameters: giving a recurring combination a name, and making sure the second request for it costs nothing.

Named Presets

A preset bundles width, height, fit, crop, quality and format under one name. It is operator configuration — a JSON object in an environment variable, not app schema — because the right thumbnail size is a property of the deployment, not of the business domain.

STORAGE_TRANSFORM_PRESETS='{
  "thumbnail": { "width": 150, "height": 150, "fit": "cover" },
  "preview":   { "width": 600, "quality": 80 },
  "avatar":    { "width": 64, "height": 64, "fit": "cover", "crop": "attention" }
}'
GET /api/buckets/photos/files/{key}?preset=avatar
GET /api/buckets/photos/files/{key}?preset=thumbnail&quality=95
Situation Result
Known preset Its fields become the transform.
Explicit parameter alongside a preset The explicit value wins, field by field.
Unknown preset name 400
?preset= with none configured 400, with a message saying so.

Overrides are per-field, not all-or-nothing: ?preset=thumbnail&quality=95 keeps the preset's 150×150 cover and changes only the quality.

Preset names must be alphanumeric with single hyphens (thumbnail, hero-banner), which keeps them safe to put in a URL. Malformed JSON, a non-object value, or an illegal name fails the server at startup rather than at the first request — a typo in this variable is found on deploy, not by a user.

Caching

A transform runs once per distinct combination. The result is held in a process-local LRU cache keyed by the storage key, the transform parameters, and the negotiated format together.

Mechanism Behaviour
Cache-Control public, max-age=31536000, immutable — one year, never revalidated.
ETag Derived from the source file plus the transform parameters.
If-None-Match A match returns 304 Not Modified.
Server cache In-memory LRU, evicting least-recently-used entries when it is full.
Invalidation Deleting a file evicts every transform derived from its key.
Variable Default Meaning
STORAGE_TRANSFORM_CACHE_MAX_SIZE 256 Cache ceiling, in megabytes.

An entry larger than the whole cap is served but never retained — it would evict everything else to fit.

The one-year immutable header is safe precisely because keys are content-addressed — a stored key is <uuid>-<filename>, so a replaced file is a new key and a new URL. Nothing is ever served stale under a URL whose content changed, because that situation cannot arise.

Originals Are Never Touched

Guarantee Behaviour
Source bytes Byte-identical after any number of transform requests.
A request with no parameters The original, subject only to Accept negotiation.
?format=origin The original bytes verbatim, negotiation bypassed.
Clearing the cache Discards derived variants only; originals stay fully available.
Non-image files Transform parameters answer 400 — nothing is attempted.

Transforms are derived, disposable, and reproducible: the cache can be dropped at any moment and the next request rebuilds it. Admins can do exactly that with DELETE /api/admin/storage/transform-cache.

Recognised image types are those Sovrium can infer from the key's extension: PNG, JPEG, GIF, WebP, AVIF, SVG and ICO. SVG passes through rather than being rasterised, and is always served as an attachment — see Upload Security.

Last updated July 27, 2026

This documentation was written with AI, so errors or outdated content are possible. Sovrium is in beta — contributions and corrections are welcome.

Built with Sovrium