Vercel Blob consistent reads: freshness, concurrency, and caching are different problems
When an AI agent rereads memory.json immediately after updating it and acts on the previous state, the model is an easy suspect. The actual cause may be a CDN in front of storage. On July 14, Vercel added useCache: false to private Blob get() and presignUrl(), creating an official path that reflects the latest overwrite. The word ‘consistent’ does not collapse every correctness problem into one option: freshness, concurrent writes, authorization, and browser caching remain four separate contracts.
What changed: an origin path for a latest-value read
A write to a fresh pathname has no existing cache entry, so a default read reflects it immediately. The edge case appears when code uses allowOverwrite: true on an existing pathname. A normal read goes through Vercel's CDN and can return the previous version for up to 60 seconds.
For a read that must observe the latest write, use get(pathname, { access: 'private', useCache: false }). The option also works with presignUrl(); without the SDK, the equivalent private URL uses cache=0. Vercel's changelog says to install @vercel/blob@2.6.1 or later.
The tradeoff is explicit: bypassing the CDN is slower than a cached read and incurs Fast Origin Transfer. Treat it as a correctness tool for selected paths, not a global performance setting.
Why this matters: file storage is increasingly used as state
Immutable images and build artifacts fit a CDN well. Agent memory, session transcripts, latest reports, workflow checkpoints, and mutable JSON manifests repeatedly overwrite one pathname and immediately feed the next step. At that point Blob is being used as a small state store.
A stale read is quiet. The request succeeds and JSON parses, yet an agent repeats completed work or a saved setting appears to revert. Success-rate monitoring stays green unless you record a state version or ETag.
Classify each object as an immutable asset, mutable state, or derived snapshot. Immutable assets want versioned paths and long caches. Mutable state needs a freshness, conflict, and recovery policy. Derived snapshots need a stated staleness budget.
Contract one: scope freshness narrowly
Use origin reads for post-write verification, the next agent step, or a save-confirmation response where read-after-write correctness affects the outcome. Keep the CDN for history views, old transcripts, and reports whose staleness budget allows it.
Wrap storage calls in domain functions such as readLatestState() and readCachedSnapshot(). Record version, ETag, readMode, writeAt, and observedAt without logging sensitive contents. That makes a stale read measurable instead of mysterious.
This release is about private storage SDK reads. A browser fetching a public Blob directly still has both CDN and browser caches. Vercel recommends immutable, versioned paths for public assets instead of overwriting them.
Contract two: a consistent read does not prevent a lost update
If two workers read the same memory file, modify it, and overwrite it in sequence, useCache: false only shows the last result sooner. It cannot restore the first worker's discarded change. Freshness is a read property; lost updates are a write-concurrency problem.
Vercel Blob supports ETag-based conditional writes. Pass an ETag from head() or get() into put(..., { allowOverwrite: true, ifMatch: etag }). If another writer changed the object, the SDK throws BlobPreconditionFailedError, and the application can reread, merge, retry, or surface a conflict.
For multi-agent transcripts and checkpoints, consider append-only events, per-writer files, or versioned snapshots plus a pointer. One mutable JSON file can turn initial convenience into recovery and audit cost.
Contract three: origin freshness and browser delivery are separate
A private Blob is commonly streamed by a Function route after user authentication. Even if get() retrieves the latest origin value, a wrong response Cache-Control can leave a browser showing an old response. Storage freshness and delivery freshness are separate hops.
Vercel recommends Cache-Control: private, no-cache for private content. The browser may store it but must revalidate; forwarding ETag and If-None-Match enables a 304 when unchanged. Use private, no-store for tokens, banking data, PII, or content that must not remain on disk.
Authorization is another boundary. Vercel recommends checking permissions in the route handler next to get(), rather than relying only on middleware. useCache: false adds no authorization, and presigned URL scope and expiry remain separate decisions.
Community signal: a stable URL quietly becomes mutable state
A Vercel Community SDK discussion paired the convenience of disabling random suffixes—reconstructing a pathname without storing a full URL—with the surprise that an overwritten avatar could remain stale in edge and browser caches. The thread is an audience signal about developer confusion, not the source of the current guarantee.
The common mistake is treating ‘a convenient stable URL’ and ‘a pointer that instantly reflects the latest state’ as the same requirement. Human-facing assets are simpler when immutable and versioned. An internal latest pointer can use a stable path, but it then needs an explicit freshness budget, origin-cost policy, and concurrency control.
Ask whether the object is content or state, how much staleness is allowed, how many writers exist, and what wins after a conflict before asking whether to disable caching.
Practical checklist
1. Classify private Blob paths as immutable assets, mutable state, or derived snapshots.
2. Find every allowOverwrite: true call and document its staleness budget.
3. Add useCache: false only to agent memory, session checkpoints, and post-write verification that require the newest value.
4. Confirm @vercel/blob is at least 2.6.1 and update the runtime lockfile.
5. For multiple writers, add ETag plus ifMatch and test the conflict path.
6. Keep authorization next to get() and set browser responses to private, no-cache or no-store by sensitivity.
7. Observe version, ETag, readMode, writeAt, and observedAt without logging the contents.
8. Test write v1 → cached read → overwrite v2 → latest read and assert that v2 is returned.
9. Monitor origin-bypass share and Fast Origin Transfer so correctness reads do not spread to all traffic.
10. Prefer new paths or random suffixes for public media and long-lived files.
Risks and counterarguments
The latest value is not always necessary. Dashboards and historical reports may tolerate a slightly old snapshot; routing all reads to origin increases latency and transfer cost without improving a user decision. Consistency should be a per-data SLO.
Conversely, ‘wait up to 60 seconds’ can be unsafe when the next workflow step repeats an external API call or payment based on an old checkpoint. Then staleness becomes a correctness, cost, and idempotency problem rather than a cosmetic delay.
As mutable coordination grows, database transactions, row locks, queries, and schemas become valuable. Blob can be a good fit for small file-shaped state, but Postgres or a dedicated state store may be simpler once multiple writers, partial updates, or strong transactions are required.
Minimal latest-read and conflict-control pattern
import { get, put, BlobPreconditionFailedError } from '@vercel/blob';
const current = await get('agents/memory.json', {
access: 'private',
useCache: false,
});
try {
await put('agents/memory.json', nextMemory, {
access: 'private',
allowOverwrite: true,
ifMatch: current.blob.etag,
});
} catch (error) {
if (error instanceof BlobPreconditionFailedError) {
// Re-read, merge, or surface the conflict.
}
throw error;
}Latest reads and conditional writes belong together, but solve different problems. On conflict, choose reread, merge, or abort according to domain policy.
Vercel Blob state-read checkpoints
✓Classify private Blob paths as immutable assets, mutable state, or derived snapshots.
✓Find every allowOverwrite: true call and document its staleness budget.
✓Add useCache: false only to agent memory, session checkpoints, and post-write verification that require the newest value.
✓Confirm @vercel/blob is at least 2.6.1 and update the runtime lockfile.
✓For multiple writers, add ETag plus ifMatch and test the conflict path.
✓Keep authorization next to get() and set browser responses to private, no-cache or no-store by sensitivity.
✓Observe version, ETag, readMode, writeAt, and observedAt without logging the contents.
✓Test write v1 → cached read → overwrite v2 → latest read and assert that v2 is returned.
✓Monitor origin-bypass share and Fast Origin Transfer so correctness reads do not spread to all traffic.
✓Prefer new paths or random suffixes for public media and long-lived files.
Bottom line
useCache: false is the missing read-after-write tool when private Blob acts like state, but it solves freshness only. Use ETag/ifMatch for concurrency, route-local auth for access, and Cache-Control or versioned URLs for delivery caching. Separating the four contracts turns ‘the agent forgot’ into a storage behavior the team can test and observe.