fix(image-optimizer): respect updated upstream Cache-Control when reusing cached image#94143
Open
itoudium wants to merge 1 commit into
Open
Conversation
…sing cached image When an optimized image becomes stale and is revalidated, the upstream source is re-fetched. If the source bytes are unchanged (same upstream etag), Next.js reuses the previously optimized buffer to skip re-encoding (vercel#67257). However it also reused the previous cache entry's revalidate value as the maxAge, which froze the cache TTL at the value computed on the very first fetch. As a result, changing the upstream/CDN Cache-Control max-age was never reflected as long as the source image bytes stayed the same. Return the maxAge derived from the freshly re-fetched upstream Cache-Control header instead. The buffer reuse (CPU optimization) is preserved; only the TTL now tracks the current upstream header.
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.
What?
When an optimized image is revalidated and the upstream source bytes are
unchanged (same upstream etag), Next.js reuses the previously optimized buffer
to skip re-encoding. This PR makes that path return the
maxAgederived fromthe freshly re-fetched upstream
Cache-Controlheader, instead of therevalidatevalue persisted in the previous cache entry.Why?
The optimized-image cache TTL was effectively frozen at the value computed on
the very first fetch. Because the reuse branch returned the previous entry's
revalidate, changing the upstream/CDNCache-Controlmax-agewas neverreflected as long as the source image bytes stayed identical — each
revalidation kept re-emitting the original TTL.
This reuse optimization was introduced in #67257; the buffer reuse itself is
correct (the optimized output is identical), but carrying over the old TTL was
an unintended side effect.
Reproduction
images: { minimumCacheTTL: 0 }, a remote/internal image whose upstreamresponds with
Cache-Control: max-age=2.next build && next start, request/_next/image?url=...&w=64&q=75→response
Cache-Controlreportsmax-age=2.Cache-Control: max-age=600without changingthe image bytes.
max-age=2; the new value isnever picked up.
(Originally observed under
output: 'standalone', but this is not specific tostandalone. The bug is in Next.js's built-in Image Optimization cache, which runs
on any self-hosted server (
next startandoutput: 'standalone'alike). It doesnot affect managed/minimal-mode deployments, where image optimization is handled
by the platform rather than this code path.)
How?
In
imageOptimizer(), the reuse branch returnedopts?.previousCacheEntry?.cacheControl?.revalidate || maxAge. The freshlycomputed
maxAge(alreadyMath.max(minimumCacheTTL, getMaxAge(upstream.cacheControl)))is now returned directly, so the buffer reuse / CPU optimization from #67257 is
preserved while the cache TTL tracks the current upstream header.
Added a unit test (
test/unit/image-optimizer/image-optimizer-max-age.test.ts)covering both the updated-
max-agecase and theminimumCacheTTLclamp. Thetest fails before the change (
Received: 31536000) and passes after.