Skip to main content
This page covers the cache option: what Cache-Control an object is served with, where to set it, and which value to pick.
Cache-Control is written once, at upload, and stored with the object. The CDN and the browser honor it on every read. There is no per-request override; changing it means writing the object again.

Which value to pick


The cache option

A duration is converted to whole seconds, so '1500ms' stores max-age=1. The grammar is on Types.

The raw header

Anything containing = or , is treated as a raw header and stored as written. Use this for s-maxage, stale-while-revalidate, no-transform and anything else the three keywords do not cover.

revalidate versus a short max-age

'revalidate' stores public, max-age=0, must-revalidate. The cached copy is checked with If-None-Match on every read, so an unchanged object costs a 304 with no body. A short max-age serves stale bytes until it expires, then re-downloads the whole object. 'revalidate' costs a round trip per read, but is never stale and never downloads the bytes twice.

Where you can set it

Four places. The most specific one wins.

On the bucket

lib/blob.ts
The default for every object this bucket stores.

On a put

updateJson takes it too, for the object it rewrites. So do copy and move, for the destination. Without it the source’s value carries over.

On a signed upload URL

Signed into the URL and handed back in headers, so the uploader has to send it verbatim.

On a direct browser upload

lib/uploads.ts
Decided per upload on your server and signed into the presigned PUT. See Upload handler.

Private buckets

On a private bucket, private replaces public, so no shared cache keeps a copy of an object only a signed request may read. This follows the bucket’s visibility in the console; nothing in the code declares it. A raw header string is passed through as written, visibility included: cache: 'public, max-age=60' on a private bucket stores public, max-age=60.

Immutable plus a versioned URL

app/api/avatar/route.ts
versionedUrl is url with the etag on the query, so it changes whenever the content does. A stable path stored immutable and served through versionedUrl is cached for a year, and every overwrite produces a URL no cache has seen. The path never moves, so nothing has to be deleted. url and versionedUrl are both undefined on a private bucket.

no-store and signed reads

These are two separate mechanisms. The link expires at expiresAt, but the stored Cache-Control outlives it: with a long max-age the reader’s browser keeps the bytes after the link stops working. If a reader must not keep the bytes, store the object with no-store. no-store drops the visibility scope entirely and stores no-store on public and private buckets alike. See signedReadUrl for link lifetimes.

What the upload route itself caches

An upload route’s GET serves its constraints document with a 60 second Cache-Control of its own, unrelated to the objects the route stores. See Constraints.