Repository navigation
feat(rsc): allow rejecting server references during decodeReply/decodeAction - #1520
Merged
Merged
Conversation
james-elicx
marked this pull request as ready for review
October 19, 2026 10:56
…tion-reachability example The progressive form path rejects server references that the current route cannot reach through the `onServerReference` callback of `decodeAction` and `decodeFormState`, so React's own decoder does the checking instead of a pre-decode copy of its `$ACTION_*` field parsing. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…example `decodeReply` also receives the `onServerReference` validator, so a server function passed as an action argument has to be reachable from the route that runs the action, just like the progressive path. New e2e cases cover a retained action passed as an argument from another route, and a progressive form whose shared action is reachable from the replay target while the server function bound to it is not. Co-Authored-By: Claude Opus 5.5 <[email protected]>
The README walks through manifest generation, request redispatch, and the decode-time check for nested server references and progressive forms, each with a figure. It also links the comparable Next.js behavior for progressive form validation and for nested server references after navigation. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…ppers `src/types` mirrors upstream React types, so the Vite-specific `extraOptions` parameter is declared on the `decodeReply` and `decodeReplyFromBusboy` wrappers, like `renderToReadableStream`'s, instead of on `DecodeReplyFunction` and `DecodeReplyFromBusboyFunction`. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
…rence Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
…action-reachability example `decodeFormState` decodes the last `$ACTION_REF_*` field, while `decodeAction` may use a later `$ACTION_ID_*` field, so a crafted progressive body can reach a server reference that `decodeAction` never checked. The example responds with 404 when that check fails, and a new e2e case posts such a body built from the server-rendered `/b` and `/c` forms. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…ty example `onServerReference` runs for every server reference React resolves through the manifest, so `decodeFormState` is held to the same route by construction. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
hi-ogawa
approved these changes
Oct 20, 2026
hi-ogawa
left a comment
Contributor
There was a problem hiding this comment.
Thanks! This API makes sense. I updated examples/action-reachability to use this API instead of probing FormData and this also now covers the case of hydrated action arguments being unreachable actions.
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.
Description
decodeReply,decodeAction, anddecodeFormStatealways decode with the plugin's server manifest, which resolves any server reference the client sends. A framework can't limit which server functions a request is allowed to decode, for example to the actions its current route exposes. Next.js does this with a page-scoped server module map, so a server function passed as an argument, or bound to a form action, has to belong to the current page.Today a framework has to skip these wrappers and call
@vitejs/plugin-rsc/vendor/react-server-dom/server.edgedirectly with its own manifest built on top ofcreateServerManifest().This adds an experimental
onServerReferencecallback in an optional last parameter, alongside the existingonClientReferenceextension onrenderToReadableStream:The callback receives
{ id, name }for each server reference React resolves while decoding. That includes the action itself indecodeAction, and any server function passed as an argument or bound to an action. Throwing rejects the reference before its module loads, and decoding fails.decodeReplyFromBusboyin@vitejs/plugin-rsc/rsc/server.nodegets the same parameter. Without the callback, nothing changes.loadServerActiondoesn't call it, so for fetch actions a framework still checks the action id itself. The README says so.Tests
New
e2e/server-reference-callback.test.ts, an inlinestarter-extrafixture, in dev and build. Its RSC entry rejects one named server reference. Two forms bind a recording action to an allowed and a rejected server function, and each is submitted twice:decodeReply);decodeAction).The rejected submissions fail with a 500 and never run the action. The allowed ones run with the bound server function. Without the source change, the rejected submissions return 200 and run the action.
decodeFormStatepasses the callback through too, but the test doesn't exercise it. React only decodes there foruseActionStateforms ($ACTION_KEY), anddecodeActionalready rejects the same bound references first.