fix(app-router): include router state in RSC navigation cache keys#2099
Open
NathanDrake2406 wants to merge 4 commits into
Open
fix(app-router): include router state in RSC navigation cache keys#2099NathanDrake2406 wants to merge 4 commits into
NathanDrake2406 wants to merge 4 commits into
Conversation
App Router client navigations could issue RSC requests with a bare _rsc query because no router-state variant header was present. That diverged from Next.js, where normal flight requests always include Next-Router-State-Tree and therefore get a non-empty cache-busting value. Serialize the current visible vinext router state into Next-Router-State-Tree before computing the RSC request URL so the header and query hash stay in lockstep. Port the upstream rsc-query-routing redirect and rewrite coverage to assert the same observable request URLs.
commit: |
Prefetch RSC requests were missing the Next-Router-State-Tree header, so their _rsc URL hash was computed without the router state projection. This diverged from Next.js behavior, where all flight requests (both navigation and prefetch) include the current router tree (confirmed in the segment-cache prefetch path at next.js/packages/next/src/client/ components/segment-cache/cache.ts:2179). Add a getRscStateTreeHeaderValue function to NavigationRuntimeFunctions and wire it into the prefetch path so prefetch URLs are derived from the same variant inputs as navigation requests. Also fixes the minor comment typo in the rewrite test assertion.
The previous commit sent Next-Router-State-Tree on imperative prefetch and navigation RSC requests, but <Link> automatic/intent prefetch still computed its RSC URL without the header. That left automatic Link prefetch and live navigation using different _rsc variant contracts. Set the header before every createRscRequestUrl() call inside the Link prefetch path, including the prefetchInlining loading-shell request, using the same runtime helper that navigation uses. This makes prefetch and navigation URLs derive from the same state snapshot. Add unit tests that verify the header is present and the resulting _rsc query value is a non-empty hash rather than a bare ?_rsc.
…ntime The runtime type gained an optional getRscStateTreeHeaderValue function, but the ambient-state validator in navigation-runtime.ts was not checking it. A malformed non-function value could therefore pass validation and throw when later accessed through getNavigationRuntime(). Add the optional-function check so the validation boundary stays consistent with the type contract.
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
Next-Router-State-Treevalue before computing App Router RSC navigation and prefetch URLs._rscURL hash are created from the same state snapshot.packages/vinext/src/server/app-browser-entry.ts,packages/vinext/src/shims/link.tsx,packages/vinext/src/shims/navigation.ts,packages/vinext/src/client/navigation-runtime.ts,tests/e2e/app-router/nextjs-compat/rsc-query-routing.spec.ts,tests/link-navigation.test.ts_rsccache-busting values, so redirected and rewritten request URLs match the upstream observable contract.Why
App Router Flight requests need a cache key that reflects router state. Next.js sends
Next-Router-State-Treeon normal RSC navigations and prefetches and uses that header when computing the_rscquery value. Vinext was missing that state input for ordinary navigations and prefetches, so source redirect/rewrite requests could be emitted as a bare?_rscand fail the upstream request-routing contract.Next-Router-State-Treebefore callingcreateRscRequestUrl().Next-Router-State-Treevalue in<Link>automatic/intent prefetch,router.prefetch(), and the prefetch-inlining loading-shell path.rsc-query-routingbehavior and assertions./redirect,/redirect/dest,/rewrite, and/rewrite/destfixture routes with matching link and heading content.What changed
/redirect/source_rscvalue, so the upstream?_rsc=filter missed it.?_rsc=<hash>./rewrite/source_rscvalue, so the upstream assertion saw no matching request.?_rsc=<hash>.<Link>automatic/intent prefetch_rscvalue and omittedNext-Router-State-Tree.?_rsc=<hash>caused by the state-tree header.router.prefetch()<Link>prefetch: state-blind_rsc._rsccontract as live navigation.Maintainer review path
packages/vinext/src/server/app-browser-entry.tschecks the state header projection and where it is added before URL hashing.packages/vinext/src/shims/link.tsxchecks the<Link>prefetch path (normal and prefetch-inlining shell).packages/vinext/src/shims/navigation.tschecks imperativerouter.prefetch().packages/vinext/src/client/navigation-runtime.tschecks the runtime validation boundary.tests/e2e/app-router/nextjs-compat/rsc-query-routing.spec.tschecks the ported upstream browser assertions.tests/link-navigation.test.tschecks that Link prefetch URLs carryNext-Router-State-Treeand a non-empty_rscvalue.tests/fixtures/app-basic/next.config.tsand the new/redirectand/rewritefixture pages check the route structure used by the spec.Validation
vp checkvp test run tests/link-navigation.test.ts tests/app-rsc-cache-busting.test.tsvp run vinext#buildPLAYWRIGHT_PROJECT=app-router pnpm run test:e2e -- tests/e2e/app-router/nextjs-compat/rsc-query-routing.spec.tsvp env exec --node 24 ./scripts/run-nextjs-deploy-suite.sh /Users/nathan/Projects/vinext/.refs/nextjs-v16.2.6 --retries 0 -c 1 --debug test/e2e/app-dir/rsc-query-routing/rsc-query-routing.test.tsRisk / compatibility
_rscto hashed_rscwhen no other variant header is present.Next-Router-State-Treeas an RSC vary input and does not parse the value.FlightRouterStatetuple because vinext does not maintain that tuple shape, and the server only hashes the header value.Non-goals
Next-Url.References
Next-Router-State-Tree._rscis derived from router request headers._rscthrough rewrite-style request routing matters.