Skip to content

Vercel Blob addRandomSuffix, allowOverwrite, and stale files in the cache

Blob refuses to overwrite by default, and an overwrite can take 60 seconds to show plus whatever the browser cached. Treat blobs as immutable; use new pathnames, not overwrites.

Vercel Blob2 min readships at docs/solutions/vercel-blob/addrandomsuffix-allowoverwrite-and-the-cache.md

Tags: vercel-blob · caching · cdn · allowOverwrite · addRandomSuffix · cacheControlMaxAge

Two symptoms, one cause.

  • put() throws because the blob "already exists".
  • You set allowOverwrite: true, upload a new avatar, and users still see the old one.

The defaults

  • allowOverwrite is false. Writing to an existing pathname throws. This is a guard, not a bug.
  • addRandomSuffix is false. Your pathname is used as given. With true, avatar.jpg becomes something like avatar-oYnXSVczoLa9yBYMFJOSNdaiiervF5.jpg.
  • cacheControlMaxAge is one month. The minimum is 60 seconds.

Why overwrites look broken

Every blob, public or private, is cached on Vercel's CDN. When you overwrite or delete one:

  • The CDN can take up to 60 seconds to drop the old copy.
  • Browsers keep their own copy for cacheControlMaxAge. The CDN updating does not reach a browser that already has the file.

So the new avatar exists, and the user still sees the old one.

The fix: never overwrite

Treat blobs as immutable. New content, new pathname.

const key = `${userId}/avatars/${crypto.randomUUID()}-${name}`;
await put(key, file, { access: "private", allowOverwrite: false });
await db.update(users).set({ avatarKey: key }).where(eq(users.id, userId));
await del(previousKey); // then clean up

The URL changes, so no cache anywhere can serve the old bytes. The long default cache becomes a feature: repeat views are fast and cheap.

addRandomSuffix or your own uuid?

Either gives unique pathnames. Pick one:

  • Your own uuid when you need to know the pathname before the upload finishes, for example to validate it on the server first. With client uploads, the token is bound to the pathname the client names, so a server that wants to check it must mint it.
  • addRandomSuffix: true when you do not care what the pathname is. Read the final one from the put() or upload() result, never assume it.

Do not use both. A suffix on top of a uuid only makes keys longer.

When overwriting is right

A single JSON file refreshed on a schedule, where the URL must stay the same. Then:

  • allowOverwrite: true.
  • A short cacheControlMaxAge, like 60 to 300 seconds.
  • For writes that must never race, ifMatch with the ETag you read. A mismatch throws BlobPreconditionFailedError.
  • On a private store, get(pathname, { access: "private", useCache: false }) reads the latest version straight from origin. It is slower and costs Fast Origin Transfer on every call, so use it only where freshness matters.

Never allow overwrite on a client token

allowOverwrite: true in onBeforeGenerateToken means a leaked or replayed token can replace a file that already exists. Keep it false for anything a browser uploads.