Re-implement Server Action reducer (#86367)#9
Re-implement Server Action reducer (#86367)#9MitchLewis930 wants to merge 1 commit intopr_039_beforefrom
Conversation
Rewrite of the Server Action reducer to use the PPR/Segment Cache navigation implementation, rather than the old lazy fetch implementation. Server Actions may trigger a revalidation, a redirect, or both. They may also invalidate the cache. The behavior could be naively implemented using router.refresh() and router.push(). Semantically, the routing behavior is equivalent. The main difference is that the server that invokes the action may also send back new data for the page within the same response. Compared to a separate request, this data is more likely to be consistent with any data that may have been mutated by the action, due to global data propagation races. (It's also faster since it avoids an extra server waterfall.) So, navigations initiated by a Server action must be able to "seed" the navigation with the data it just received from the server. I've added a new internal method, navigateToSeededRoute, that implements this behavior.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.
| return handleMutable(state, mutable) | ||
| const pendingPush = redirectType !== RedirectType.replace | ||
| state.pushRef.pendingPush = pendingPush | ||
| mutable.pendingPush = pendingPush |
There was a problem hiding this comment.
Incorrect pendingPush when action has no redirect
Medium Severity
When a server action triggers revalidation but no redirect, pendingPush is incorrectly set to true. The calculation redirectType !== RedirectType.replace evaluates to true when redirectType is undefined (no redirect occurred). This causes the router to push a new history entry when navigating after a revalidation-only action, potentially creating duplicate history entries for the same URL. The fix is to only set pendingPush to true when there's an actual redirect that isn't a replace — e.g., redirectLocation !== undefined && redirectType !== RedirectType.replace.


PR_039
Note
Medium Risk
Touches core client routing/server-action/refresh flows and changes how dynamic refetching and redirects are modeled, which can affect navigation correctness and cache consistency (especially with interception routes).
Overview
Reworks Server Action handling to drive updates through the Segment Cache navigation path. The
serverActionReducernow decides between no-op, external redirect (MPA), seeded navigation (using Flighttree/seedData/headfrom the action response), or a normal segment-cache navigation, and it refreshes dynamic data when the action revalidated cache.Refreshes are re-modeled as
navigateToSeededRoute(...)withshouldRefreshDynamicData=trueand interception-awarenextUrlselection.Navigation internals were extended to support seeded data/head (favoring action-provided RSC/head over separate fetches), pass an explicit
shouldRefreshDynamicDataflag through navigation, and request full-route dynamic data for unprefetched refresh/revalidation cases.Server RSC payloads now include
q(rendered search) andi(could-be-intercepted) for both navigation and action responses, and the client consumes these to seed navigations correctly.Written by Cursor Bugbot for commit e760fa2. This will update automatically on new commits. Configure here.