Describe the bug
When enableCacheInterception is enabled, an ISR cache miss causes the incremental cache to be queried twice for the same key within a single request.
cacheInterceptor fetches the entry, and on a miss it returns the event without passing the (empty) result downstream. NextServer then starts and the cache handler fetches the very same key again:
None of the four return event paths carry the fetched result, so the second lookup is unavoidable.
On a hit this costs nothing, but on a miss the store round-trip is paid twice before any rendering starts.
Observed impact
Running on Cloudflare Workers with the R2 incremental cache, using Workers automatic tracing. Every ISR miss shows two cache_match / r2_get pairs before the API fetches begin:
cache_match (regional cache) 4 ms
r2_get 288 ms <- cacheInterceptor
cache_match (regional cache) 3 ms
r2_get 306 ms <- NextServer cache handler
fetch (API, parallel) 212 ms
r2_put 1,633 ms
Five consecutive traces of different pages all showed exactly two r2_get spans, 274–319 ms each. That is roughly 290 ms of avoidable latency per miss, about 20–25 % of the total request time in our case.
Sites where most requests hit the cache will not notice this. Sites with a long tail of rarely-visited pages (ours has one page per restaurant) miss often, so the duplicate lookup is paid on a large share of requests.
Steps to reproduce
- Deploy an app with
enableCacheInterception: true and an incremental cache backed by remote storage
- Request an ISR page whose entry is not in the cache
- Observe two lookups for the same key — via tracing, or by logging inside the incremental cache override's
get
Expected behavior
The key should be fetched once per request. Some options:
- Pass the interceptor's result (including the miss) to the downstream cache handler
- Memoize
incrementalCache.get for the duration of a request
Request-scoped memoization would not have the correctness problem discussed in #621, since it cannot outlive a single request and therefore cannot serve data invalidated by on-demand revalidation.
Versions
@opennextjs/aws: 4.0.2 (behaviour confirmed unchanged on main)
@opennextjs/cloudflare: 1.20.1
- Next.js: 15
- Deployed on Cloudflare Workers
I have not measured this on AWS/S3, but the code path is shared.
Describe the bug
When
enableCacheInterceptionis enabled, an ISR cache miss causes the incremental cache to be queried twice for the same key within a single request.cacheInterceptorfetches the entry, and on a miss it returns the event without passing the (empty) result downstream.NextServerthen starts and the cache handler fetches the very same key again:cacheInterceptor.ts—const cachedData = await globalThis.incrementalCache.get(cacheKey), thenif (!cachedData?.value) return eventadapters/cache.ts—const cachedEntry = await globalThis.incrementalCache.get(key, "cache")None of the four
return eventpaths carry the fetched result, so the second lookup is unavoidable.On a hit this costs nothing, but on a miss the store round-trip is paid twice before any rendering starts.
Observed impact
Running on Cloudflare Workers with the R2 incremental cache, using Workers automatic tracing. Every ISR miss shows two
cache_match/r2_getpairs before the API fetches begin:Five consecutive traces of different pages all showed exactly two
r2_getspans, 274–319 ms each. That is roughly 290 ms of avoidable latency per miss, about 20–25 % of the total request time in our case.Sites where most requests hit the cache will not notice this. Sites with a long tail of rarely-visited pages (ours has one page per restaurant) miss often, so the duplicate lookup is paid on a large share of requests.
Steps to reproduce
enableCacheInterception: trueand an incremental cache backed by remote storagegetExpected behavior
The key should be fetched once per request. Some options:
incrementalCache.getfor the duration of a requestRequest-scoped memoization would not have the correctness problem discussed in #621, since it cannot outlive a single request and therefore cannot serve data invalidated by on-demand revalidation.
Versions
@opennextjs/aws: 4.0.2 (behaviour confirmed unchanged onmain)@opennextjs/cloudflare: 1.20.1I have not measured this on AWS/S3, but the code path is shared.