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

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.

```ts
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.

---

Agentic Boilerplate: A Next.js repo your agent already knows. Free during launch, then $99 once.

- Site map for agents: https://agenticboilerplate.com/llms.txt
- Public API: https://agenticboilerplate.com/openapi.json
- Contact: agenticstudio@gmail.com
