vercel / vercel/next.js

cachedNavigations session-shell prerender races an async cacheHandlers.default get: E1163 on every dynamic render with deterministic keys

Open
#98,543 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Cache Components Runtime
Dominant language
JavaScript
Stars
142k
Forks
32.4k
Avg merge
2d 14h
Merged PRs (30d)
351

Description

Link to the code that reproduces this issue

https://github.com/vicanera/next-cached-navigations-cache-handler-race

To Reproduce
  1. Next 16.3.4, cacheComponents: true, partialPrefetching: true, and a cacheHandlers.default whose get awaits I/O. In the linked repo slow-cache-handler.js wraps Next's own default in-memory handler and adds a few milliseconds of latency to get at runtime, standing in for a Redis/Memcached handler.
  2. A page whose cached reads arrive in more than one batch (a second batch of "use cache" reads starts only after the first batch has resolved), with a cookie-bound child behind <Suspense> so the route is a dynamic render with a static shell.
  3. next build && next start, then request the page repeatedly with NEXT_PRIVATE_DEBUG_CACHE=1.

In a production app with this configuration and a Redis-backed handler, roughly half of the renders of the affected page log three E1163 warnings (one per cached read in the second batch); routes whose reads all start in one batch never do. Fallback-param renders additionally log NEXT_STATIC_GEN_BAILOUT ("Next.js encountered uncached or runtime data during prerendering") from the same spawned prerender.

Honest status of the linked repo: the isolated shape there has not yet fired the warning locally (0/30 at 5 ms and 20 ms handler latency). The debug trail below is from the production app on the same Next version; the exact batching that opens the window has not been reduced further. Included because the source-level mechanism is deterministic and the workaround is verified.

Current vs. Expected behavior

Current. With experimental.cachedNavigations (auto-enabled under cacheComponents) and Partial Prefetching on, every dynamic render (renderToStream for documents, generateStagedDynamicFlightRenderResultNode for RSC navigations) installs a CacheSignal and, on cacheSignal.cacheReady(), spawns spawnRuntimePrefetchWithFilledCaches (mode rewindable-session-shell). cacheReady() is a quiescence heuristic (cache-signal.js: read count 0 → setImmediatesetTimeout(0) with no new beginRead). In use-cache-wrapper.js, a cache-handler hit is written into the resume data cache only after await cacheHandler.get() (the "leader resolved with cache handler hit" path), whereas a generated entry is registered before the function runs (saveToResumeDataCache). So when a render's reads come in two batches, the count touches zero between them; with an async handler the spawned prerender then looks keys up while the second batch's gets are still pending, misses, warns:

Error: Unexpected cache miss after cache warming phase during prerendering. This is likely caused by non-deterministic arguments ...   (E1163)

and demotes the segment to a runtime hole. Every miss shows the same ordering for the affected key (NEXT_PRIVATE_DEBUG_CACHE=1):

use-cache: Resume Data Cache entry not found [key]        <- the render's own lookup
use-cache: registering as intra-request invocation leader [key]
use-cache: registering as cross-request invocation leader [key]
use-cache: Resume Data Cache entry not found [key]        <- the spawned prerender's lookup
Error: Unexpected cache miss after cache warming phase during prerendering ...
use-cache: leader resolved with cache handler hit [key]   <- the handler get resolves last

A request that does not miss shows leader resolved with cache handler hit before the second lookup. The arguments are deterministic; the message's diagnosis (non-deterministic arguments) does not apply. With the default in-memory handler get resolves within the same tick, so the race never shows there.

Expected. No warning and no demotion when cache keys are deterministic and the handler simply takes a few milliseconds. Either the pending handler read should hold the CacheSignal until the entry is registered in the resume data cache, or a handler hit should be registered in the resume data cache as a pending entry before the get is awaited, as the generated-entry path already does.

Workaround. experimental: { cachedNavigations: false } → 0 warnings over 85 requests in the production app; prefetch-driven navigations (next-router-prefetch 2/3, which take generateRuntimePrefetchResult) unaffected. The same code shape is present in 16.4.0-canary.26.

Provide environment information
Operating System:
  Platform: darwin (production: linux x64 container)
  Arch: arm64
Binaries:
  Node: 26.2.0
  npm: 11.13.0
Relevant Packages:
  next: 16.3.4
  react: 19.2.0
  react-dom: 19.2.0
Next.js Config:
  cacheComponents: true, partialPrefetching: true, cacheHandlers.default: custom (async get)
Which area(s) are affected? (Select all that apply)

Cache Components, Partial Prefetching / Runtime Prefetching, Use Cache

Which stage(s) are affected? (Select all that apply)

next start (local), Other (deployed to a self-hosted container)

Additional context

Also reproduced with a real Redis-backed cacheHandlers.default pointed at a throwaway redis:7-alpine: 45 warnings over 30 requests of the affected page on the merged build, 0 over 85 with cachedNavigations: false. Happy to add the debug trail of a failing request in full if useful.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the linked reproduction, then read cache-signal.js and use-cache-wrapper.js around cacheReady(), handler get(), and resume-data registration. Trace renderToStream and generateStagedDynamicFlightRenderResultNode with an async cache handler and two read batches. Done means deterministic keys produce no E1163 warnings or runtime-hole demotions without disabling cachedNavigations.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, next.js, react, redis
Domain
backend, performance, web-dev
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.