Skip to content

Comments

Re-implement Server Action reducer (#86367)#9

Open
MitchLewis930 wants to merge 1 commit intopr_039_beforefrom
pr_039_after
Open

Re-implement Server Action reducer (#86367)#9
MitchLewis930 wants to merge 1 commit intopr_039_beforefrom
pr_039_after

Conversation

@MitchLewis930
Copy link

@MitchLewis930 MitchLewis930 commented Jan 30, 2026

PR_039

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced navigation performance with improved data seeding and caching strategies.
    • Refined refresh behavior to intelligently re-fetch dynamic content while preserving application state.
    • Strengthened interception route detection and handling during navigation.
  • Improvements

    • Better preservation of search query strings during page navigation.
    • More efficient handling of route transitions with optimized cache management.

✏️ Tip: You can customize this high-level summary in your review settings.

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.
@coderabbitai
Copy link

coderabbitai bot commented Jan 30, 2026

📝 Walkthrough

Walkthrough

This pull request implements seeded navigation with dynamic refresh capabilities, adding seedData and seedHead parameters throughout PPR navigation routines, refactoring refresh operations to use navigateToSeededRoute as a seeded navigation variant, and extending RSC payloads with rendered search queries and interception detection metadata.

Changes

Cohort / File(s) Summary
PPR Navigation Seeding
packages/next/src/client/components/router-reducer/ppr-navigations.ts
Extends startPPRNavigation, updateCacheNodeOnNavigation, and createCacheNodeOnNavigation with seedData and seedHead parameters. Threads seed data through recursive cache node updates and child path logic. Updates NavigationRequestAccumulation to allow null scrollableSegments. Adjusts segment cloning behavior based on shouldRefreshDynamicData flag.
Navigation Dispatch Updates
packages/next/src/client/components/router-reducer/reducers/navigate-reducer.ts
Introduces shouldRefreshDynamicData flag set to false and passes it to navigateUsingSegmentCache call.
Refresh-as-Navigation Refactoring
packages/next/src/client/components/router-reducer/reducers/refresh-reducer.ts
Replaces refreshUsingSegmentCache with navigateToSeededRoute, reframing refresh as a seeded navigation. Introduces nextUrlForRefresh computation using interception route detection and adds seedFlightRouterState, seedRenderedSearch, seedData, seedHead, and refresh/scroll flags to navigation call.
Server Action Navigation Flow
packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts
Adds actionFlightDataRenderedSearch and actionFlightDataCouldBeIntercepted fields to FetchServerActionResult. Updates imports to use navigateToSeededRoute and navigateUsingSegmentCache. Refactors flow to distinguish between seeded navigation (when flight data present) and regular navigation, with early bailout for no-op cases.
Navigation Helpers Modernization
packages/next/src/client/components/segment-cache/navigation.ts
Adds shouldRefreshDynamicData parameter to navigate function to steer dynamic refresh behavior. Introduces new navigateToSeededRoute export accepting explicit seed data and route tree. Updates internal helpers to thread shouldRefreshDynamicData and seed data, and changes default scrollableSegments initialization from array to null.
RSC Payload Enrichment
packages/next/src/server/app-render/app-render.tsx
Extends generateDynamicRSCPayload to include two new fields in returned RSCPayload: q (rendered search from getRenderedSearch(query)) and i (interception indicator from couldBeIntercepted flag).
Type Definitions
packages/next/src/shared/lib/app-router-types.ts
Updates NavigationFlightResponse and ActionFlightResponse types to include q: string (renderedSearch) and i: boolean (couldBeIntercepted) fields.

Sequence Diagram

sequenceDiagram
    participant Client as Client Action/Refresh
    participant NavHelper as Navigation Helper
    participant StartPPR as startPPRNavigation
    participant UpdateCache as updateCacheNodeOnNavigation
    participant CreateCache as createCacheNodeOnNavigation
    participant Server as Server RSC

    Client->>NavHelper: navigate/refresh with seedData, seedHead
    NavHelper->>StartPPR: call with seedData, seedHead, shouldRefreshDynamicData
    
    alt Has seed data
        StartPPR->>UpdateCache: updateCacheNodeOnNavigation(seedData, seedHead, ...)
    else No seed data
        StartPPR->>CreateCache: createCacheNodeOnNavigation(seedData=null, seedHead=null, ...)
    end
    
    UpdateCache->>UpdateCache: Thread seedData/seedHead through child paths
    UpdateCache->>CreateCache: Recursive call with seedDataChild, seedHeadChild
    CreateCache->>CreateCache: Check if needsDynamicRequest based on seedData presence
    
    alt seedData available and valid
        CreateCache->>CreateCache: Use seed data instead of fresh request
    else No seed data
        CreateCache->>Server: Fetch fresh RSC with dynamic data
        Server-->>CreateCache: RSC payload with q, i fields
    end
    
    CreateCache-->>UpdateCache: Cache node created
    UpdateCache-->>StartPPR: Navigation completed
    StartPPR-->>NavHelper: Result
    NavHelper-->>Client: Navigation result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 With seeds of data now in hand,
Navigation flows through every land,
Refresh reborn as seeded quest,
Intercepting routes, the client's best!
Queries tagged with q and i—
A hoppy hop through URLs, my oh my! 🌟

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The description is minimal and lacks required sections (What, Why, How, issue/PR links) specified in the template. Expand description with What, Why, How sections and add issue/PR links (e.g., Fixes #, Closes NEXT-) as required by the template.
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: re-implementing the Server Action reducer with new navigation semantics.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch pr_039_after

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/next/src/server/app-render/app-render.tsx (1)

535-558: ⚠️ Potential issue | 🟡 Minor

Handle array-valued Vary headers when computing couldBeIntercepted.
res.getHeader('vary') can be string[], which would currently yield a false negative. Consider normalizing to cover both shapes (and keep the logic consistent with the initial payload branch in this file).

🔧 Suggested defensive handling
-  const varyHeader = ctx.res.getHeader('vary')
-  const couldBeIntercepted =
-    typeof varyHeader === 'string' && varyHeader.includes(NEXT_URL)
+  const varyHeader = ctx.res.getHeader('vary')
+  const couldBeIntercepted =
+    typeof varyHeader === 'string'
+      ? varyHeader.includes(NEXT_URL)
+      : Array.isArray(varyHeader)
+        ? varyHeader.some((value) => value.includes(NEXT_URL))
+        : false
🤖 Fix all issues with AI agents
In `@packages/next/src/client/components/router-reducer/ppr-navigations.ts`:
- Around line 276-298: The seedData handling incorrectly hardcodes
isSeedRscPartial = false, ignoring CacheNodeSeedData[3]; update the branch that
sets isSeedRscPartial (and the other similar branch around
readCacheNodeFromSeedData) to read and propagate the actual isPartial flag from
seedData (e.g., seedData[3]) into the call to readCacheNodeFromSeedData and into
the computation of needsDynamicRequest so that dynamic requests are triggered
when the seed payload is partial; touch the variables seedRsc, seedLoading,
isSeedRscPartial, isSeedHeadPartial, readCacheNodeFromSeedData, and
needsDynamicRequest to ensure the true partial state is used.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e9a03ac and e760fa2.

📒 Files selected for processing (7)
  • packages/next/src/client/components/router-reducer/ppr-navigations.ts
  • packages/next/src/client/components/router-reducer/reducers/navigate-reducer.ts
  • packages/next/src/client/components/router-reducer/reducers/refresh-reducer.ts
  • packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts
  • packages/next/src/client/components/segment-cache/navigation.ts
  • packages/next/src/server/app-render/app-render.tsx
  • packages/next/src/shared/lib/app-router-types.ts
🧰 Additional context used
🧬 Code graph analysis (4)
packages/next/src/server/app-render/app-render.tsx (1)
packages/next/src/client/route-params.ts (1)
  • getRenderedSearch (22-38)
packages/next/src/client/components/router-reducer/reducers/refresh-reducer.ts (2)
packages/next/src/client/components/router-reducer/reducers/has-interception-route-in-current-tree.ts (1)
  • hasInterceptionRouteInCurrentTree (4-38)
packages/next/src/client/components/segment-cache/navigation.ts (1)
  • navigateToSeededRoute (209-271)
packages/next/src/client/components/router-reducer/ppr-navigations.ts (1)
packages/next/src/shared/lib/app-router-types.ts (4)
  • FlightSegmentPath (220-231)
  • CacheNodeSeedData (240-249)
  • HeadData (15-15)
  • ChildSegmentMap (17-17)
packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts (5)
packages/next/src/client/flight-data-helpers.ts (1)
  • NormalizedFlightData (22-37)
packages/next/src/client/components/segment-cache/cache-key.ts (1)
  • NormalizedSearch (6-6)
packages/next/src/client/components/segment-cache/cache.ts (1)
  • revalidateEntireCache (310-332)
packages/next/src/client/components/router-reducer/reducers/navigate-reducer.ts (1)
  • handleExternalUrl (30-42)
packages/next/src/client/components/segment-cache/navigation.ts (1)
  • navigateToSeededRoute (209-271)
🔇 Additional comments (15)
packages/next/src/shared/lib/app-router-types.ts (2)

295-308: NavigationFlightResponse now includes renderedSearch/interception flags — LGTM.
These fields align the shared type with the new payload shape.


311-322: ActionFlightResponse metadata additions look good.
Matches the server/client changes for seeded navigation.

packages/next/src/client/components/segment-cache/navigation.ts (6)

69-205: navigate now threads the refresh flag & same‑page detection — LGTM.
The updated signature and propagation look consistent with the seeded navigation flow.


209-270: Seeded navigation entry point is wired correctly.
The seeded route inputs and dynamic request hookup look consistent.


273-337: Prefetch navigation path aligns with seeded inputs — LGTM.
The null seedData/seedHead + prefetch data split reads correctly.


345-364: Result mapping handles nullable scroll segments cleanly.
This keeps the downstream reducer logic intact.


566-593: No‑prefetch navigation seed resets look correct.
The explicit null seed data/head keeps the simulated prefetch flow consistent.


495-536: No changes needed. The DynamicRequestTreeForEntireRoute constant is safe as-is. The object is serialized to a JSON string in prepareFlightRouterStateForRequest before any downstream processing, and stripClientOnlyDataFromFlightRouterState creates entirely new objects without mutating its input. The original constant is only read, never modified, so mutations cannot leak across navigations. A factory function is unnecessary.

Likely an incorrect or invalid review comment.

packages/next/src/client/components/router-reducer/reducers/navigate-reducer.ts (1)

160-173: Reducer now passes explicit refresh flag — LGTM.
Keeps the call site aligned with the new navigation API.

packages/next/src/client/components/router-reducer/reducers/refresh-reducer.ts (1)

23-55: Refresh now goes through seeded navigation with interception-aware nextUrl — nice.
This reads cleanly and matches the new seeded navigation flow.

packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts (5)

35-56: Import updates align with the new navigation helpers.
No concerns here.


74-84: Action fetch result now carries renderedSearch/interception metadata — LGTM.
This matches the new payload fields for seeded navigation.


162-233: RSC action response parsing now preserves q/i metadata — looks good.
The conditional capture when flight data exists is appropriate.


291-370: Revalidation/redirect handling and early bailout flow are cleanly structured.
The updated control flow is easier to follow and keeps behavior intact.


387-449: Seeded vs. non‑seeded navigation selection looks solid.
The root-render guard and fallback navigation path are consistent.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +276 to 298
} else if (seedData !== null) {
// If this navigation was the result of an action, then check if the
// server sent back data in the action response. We should favor using
// that, rather than performing a separate request. This is both better
// for performance and it's more likely to be consistent with any
// writes that were just performed by the action, compared to a
// separate request.
const seedRsc = seedData[0]
const seedLoading = seedData[2]
const isSeedRscPartial = false
const isSeedHeadPartial = seedHead === null
newCacheNode = readCacheNodeFromSeedData(
seedRsc,
seedLoading,
isSeedRscPartial,
seedHead,
isSeedHeadPartial,
isLeafSegment,
newParallelRoutes,
navigatedAt
)
needsDynamicRequest = isLeafSegment && isSeedHeadPartial
} else if (prefetchData !== null) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Seed data ignores isPartial, risking unresolved dynamic holes.

In both seedData branches, isSeedRscPartial is hardcoded to false. If a seeded payload is partial (CacheNodeSeedData[3] === true), the code will treat it as complete and skip a dynamic request, leaving holes unresolved. Please honor the isPartial flag and propagate it into needsDynamicRequest.

🔧 Proposed fix
-    const isSeedRscPartial = false
+    const isSeedRscPartial = seedData[3]
     const isSeedHeadPartial = seedHead === null
     newCacheNode = readCacheNodeFromSeedData(
       seedRsc,
       seedLoading,
       isSeedRscPartial,
       seedHead,
       isSeedHeadPartial,
       isLeafSegment,
       newParallelRoutes,
       navigatedAt
     )
-    needsDynamicRequest = isLeafSegment && isSeedHeadPartial
+    needsDynamicRequest =
+      isSeedRscPartial || (isLeafSegment && isSeedHeadPartial)
@@
-    const isSeedRscPartial = false
+    const isSeedRscPartial = seedData[3]
     const isSeedHeadPartial = seedHead === null
     newCacheNode = readCacheNodeFromSeedData(
       seedRsc,
       seedLoading,
       isSeedRscPartial,
       seedHead,
       isSeedHeadPartial,
       isLeafSegment,
       newParallelRoutes,
       navigatedAt
     )
-    needsDynamicRequest = isLeafSegment && isSeedHeadPartial
+    needsDynamicRequest =
+      isSeedRscPartial || (isLeafSegment && isSeedHeadPartial)

Also applies to: 584-606

🤖 Prompt for AI Agents
In `@packages/next/src/client/components/router-reducer/ppr-navigations.ts` around
lines 276 - 298, The seedData handling incorrectly hardcodes isSeedRscPartial =
false, ignoring CacheNodeSeedData[3]; update the branch that sets
isSeedRscPartial (and the other similar branch around readCacheNodeFromSeedData)
to read and propagate the actual isPartial flag from seedData (e.g.,
seedData[3]) into the call to readCacheNodeFromSeedData and into the computation
of needsDynamicRequest so that dynamic requests are triggered when the seed
payload is partial; touch the variables seedRsc, seedLoading, isSeedRscPartial,
isSeedHeadPartial, readCacheNodeFromSeedData, and needsDynamicRequest to ensure
the true partial state is used.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants