fix(app-router): match Next.js cached navigation shells#1916
Draft
NathanDrake2406 wants to merge 19 commits into
Draft
fix(app-router): match Next.js cached navigation shells#1916NathanDrake2406 wants to merge 19 commits into
NathanDrake2406 wants to merge 19 commits into
Conversation
Cached navigations only reused full visited Flight responses, so static segment data from the initial HTML or a prior navigation was not staged separately. That diverged from Next.js Segment Cache behavior and broke cached-navigation freshness, especially when a data-cache hit dropped cacheLife stale metadata. Add a static-navigation-shell render mode that suspends request-bound APIs at their Suspense boundaries, seed and consume client shell entries, and preserve stale cacheLife metadata across data-cache hits so shell freshness follows the cached content contract.
commit: |
Contributor
Author
|
@codex review |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Member
|
/bigbonk review for issues |
Contributor
|
@james-elicx Bonk workflow was cancelled. View workflow run · To retry, trigger Bonk again. |
…e-cached-navigations # Conflicts: # packages/vinext/src/server/app-browser-entry.ts # packages/vinext/src/shims/server.ts # tests/next-config.test.ts
Static navigation shells and PPR fallback shells share the same underlying primitive, but the higher-level call sites in app-page-dispatch.ts and app-page-render.ts were speaking in PPR terms. Introduce a thin static-navigation-shell facade so that static navigation shell lifecycle reads as its own abstraction, while still delegating to the existing PPR fallback-shell machinery internally. This is a naming/boundary refactor only; runtime behaviour, response headers, cache keys, stale-time policy, and request API suspension semantics are unchanged.
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.
Overview
This PR matches the upstream Next.js Segment Cache cached-navigation contract for static, partially static, fully static, runtime-prefetchable, and initial-HTML-seeded App Router navigations.
The core change is a static navigation shell render stage that can be cached and replayed separately from the authoritative live navigation Flight response. Cached navigations can now replay reusable static shells immediately, while dynamic request-bound work continues to resolve through the live RSC request.
Upstream references
test/e2e/app-dir/segment-cache/cached-navigations/cached-navigations.test.tspackages/next/src/client/components/segment-cache/navigation.tscache.tsandtypes.tsuse cacheanduse cache: privateWhy
Cached navigation correctness depends on separating:
vinext previously reused full visited Flight responses, but did not have a separate cached static shell stage. It also did not preserve
cacheLife().stalemetadata from"use cache"hits, which meant shell freshness could not survive data-cache reuse.What changed
static-navigation-shellRSC mode and shell-scoped suspension forconnection,headers,cookies, and pagesearchParams.params/searchParams; they no longer infer prop usage fromFunction.length.app-browser-static-navigation-shell-cache.tsfor complete/partial shell storage, TTLs, LRU eviction, mounted-slot invalidation, initial seeding, and replay.unstable_instant.prefetch = "runtime"so request APIs can be included whileconnection()remains deferred.staleinCacheControlMetadata, including across data-cache HITs.searchParamsno longer downgrades fully static shells to partial, and complete static-shell payloads preserve build-time layout flags for later static-layout reuse.Review path
tests/e2e/app-router-bfcache/cached-navigations.spec.tsandtests/fixtures/app-bfcache/app/nextjs-compat/cached-navigations/for the upstream behavior port.packages/vinext/src/server/app-static-navigation-shell.ts,packages/vinext/src/shims/headers.ts,packages/vinext/src/shims/server.ts, andpackages/vinext/src/server/app-page-search-params-observation.tsfor request API suspension.packages/vinext/src/server/app-page-dispatch.tsandpackages/vinext/src/server/app-page-render.tsfor shell lifecycle, layout flags, and response headers.packages/vinext/src/server/app-page-element-builder.tsfor static-shell page-prop wiring.packages/vinext/src/server/app-browser-static-navigation-shell-cache.tsandtests/app-browser-static-navigation-shell-cache.test.tsfor browser shell cache policy, TTLs, mounted-slot invalidation, compatibility eviction, detached seeding, LRU behavior, initial seeding, redirect/canonical safety, empty partial shell rejection, and partial response restoration.packages/vinext/src/server/app-browser-entry.tsfor the navigation orchestration points that call the shell cache.packages/vinext/src/shims/cache.ts,packages/vinext/src/shims/cache-runtime.ts, andpackages/vinext/src/shims/thenable-params.tsfor stale metadata preservation and observed page-prop thenables.Validation
vp checkvp test run tests/app-browser-static-navigation-shell-cache.test.ts tests/app-browser-entry.test.tsvp test run tests/app-page-element-builder.test.tsvp test run tests/app-page-search-params-observation.test.ts tests/app-page-render.test.ts tests/thenable-params.test.tsvp test run tests/shims.test.ts -t 'searchParams|thenable|use cache'vp test run tests/ppr-fallback-shell.test.tsvp test run tests/next-config.test.ts -t "cachedNavigations|appShells|detectNextIntlConfig"CI=1 PLAYWRIGHT_PROJECT=app-router-bfcache vp exec playwright test tests/e2e/app-router-bfcache/cached-navigations.spec.ts --retries=0CI=1 PLAYWRIGHT_PROJECT=app-router-bfcache vp exec playwright test --shard=1/1CI=1 PLAYWRIGHT_PROJECT=app-router-bfcache pnpm run test:e2e -- cached-navigations.spec.ts --retries=0Latest head includes follow-up hardening for static-shell page props, shell compatibility, redirect/canonical URL safety, empty partial shell rejection, detached shell seeding, complete fully-static shell replay, and static-shell layout flag preservation.
Risk / compatibility
cacheComponents && experimental.cachedNavigations.stale; existingrevalidateandexpirebehavior is unchanged.searchParamsobservations are ignored only inside static-shell scope; explicit key/property access still suspends the shell.Non-goals
unstable_postpone; vinext uses a shell render compatibility path instead.