fix(cache): make on-demand tag invalidation immediate (new albums weren't showing in real time)#514
Open
Zheaoli wants to merge 1 commit into
Open
fix(cache): make on-demand tag invalidation immediate (new albums weren't showing in real time)#514Zheaoli wants to merge 1 commit into
Zheaoli wants to merge 1 commit into
Conversation
…e-revalidate
Creating a new album (and other admin writes) did not update the public
pages in real time: a new album only appeared on the home page after up
to an hour.
Root cause is the `'max'` argument passed to `revalidateTag`. In Next 16
`revalidateTag(tag, 'max')` is stale-while-revalidate, not immediate:
the `'max'` cacheLife resolves to `{ expire: 31536000 }` (one year), and
the cache handler stores `expired = now + expire * 1000` — a year out.
`areTagsExpired` only treats an entry as expired once `expiredAt <= now`,
so the tag is merely marked stale, never hard-expired. A hot entry such
as the home page album nav (`cachedAlbumsShow`) keeps being served and is
refreshed only by its own `unstable_cache` TTL, which for albums/config
is 1h. That is the "not real-time" behaviour.
Pass `{ expire: 0 }` instead, via a single `expireTag` helper. That sets
`expired = now` → immediate hard expiration → the next read refetches.
`updateTag` would also be immediate but throws outside a Server Action,
and our writes run inside the Hono route handler, so it is not usable;
the deprecated single-arg `revalidateTag(tag)` is immediate too but logs
a warning on every call. `{ expire: 0 }` is type-correct (CacheLifeConfig),
warning-free, and works from the route handler.
Note: this is required regardless of cache backend. A shared/persistent
cacheHandler (for multi-instance propagation) would still receive the
one-year duration under `'max'` and not expire immediately.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Symptom
Creating a new album did not update the public home page in real time — the new album only appeared after up to an hour.
Root cause
The on-demand invalidation helpers in
server/lib/cache.tscalledrevalidateTag(tag, 'max'). In Next 16 the two-argument form with a cacheLife profile is stale-while-revalidate, not immediate:'max'resolves to the cacheLife{ stale: 300, revalidate: 30d, expire: 365d }(config-shared.js).durations = { expire: 31536000 }and passes it to the cache handler (revalidation-utils.js). Its own comment: "If profile is not found and not 'max', durations will be undefined which will trigger immediate expiration."expired = now + expire * 1000— i.e. one year in the future (file-system-cache.js). OnlyrevalidateTag(tag)with no profile storesexpired = now.areTagsExpiredtreats an entry as expired only onceexpiredAt <= now, so the tag is merely marked stale, never hard-expired.The home page album nav (
cachedAlbumsShow, tagalbums) is a hot entry, so it kept being served and was refreshed only by its ownunstable_cacheTTL — 1h for albums/config. Hence "not real-time". This also matches the observation that a new image sometimes appeared sooner (a cold/evicted entry refetches fresh) while the new album stayed stale (hot nav entry, only marked stale).This affects all three public cache groups (
gallery/albums/config); thealbums/config1h TTL just made it most visible there.galleryalready has the 60s TTL from #513, which masked it for images.Fix
Route all invalidation through a single
expireTaghelper that passes{ expire: 0 }:{ expire: 0 }→ handler storesexpired = now→ immediate hard expiration → next read refetches.CacheLifeConfig = { expire?: number }), and no deprecation warning (unlike the single-arg form).updateTagwould also be immediate but throws outside a Server Action; our writes run inside the Hono route handler (app/api/[[...route]]/route.ts), so it is not usable here.Scope / risk
Note on a shared cacheHandler
This fix is required regardless of cache backend. A shared/persistent cacheHandler (for multi-instance propagation) would still receive the one-year duration under
'max'and not expire immediately, so dropping'max'is a prerequisite. Whether a shared cacheHandler is additionally needed depends on whether the deployment runs multiple instances — tracked separately.