Skip to content

Conversation

@agadzik
Copy link
Contributor

@agadzik agadzik commented Dec 9, 2025

What?

When calling the redirect function in a server action from within a intercepted parallel route, the intercepted route's content would remain visible on screen, and the redirect would never happen

Why?

The bug stems from intentional behavior in the applyPatch function (in apply-router-state-patch-to-tree.ts) that preserves parallel route state during navigation:

// if the applied patch segment is __DEFAULT__ then it can be ignored in favor of the initial tree
// this is because the __DEFAULT__ segment is used as a placeholder on navigation
if (
  patchSegment === DEFAULT_SEGMENT_KEY &&
  initialSegment !== DEFAULT_SEGMENT_KEY
) {
  return initialTree
}

This logic exists for a good reason: parallel routes should persist across navigations. For example, if you have a sidebar (@sidebar) and navigate between pages, the sidebar content should remain visible rather than being replaced with __DEFAULT__ (which would render default.tsx / null).

The Problem with Server Action Redirects

When a server action calls redirect('/'):

  1. Current tree has the intercepted modal:

    modal: ["(slot)", { children: ["(.)about", ...] }]
    
  2. Server response contains __DEFAULT__ for the modal slot:

    modal: ["__DEFAULT__", {}]
    
  3. applyPatch sees: patchSegment === "__DEFAULT__" and initialSegment === "(slot)" (not __DEFAULT__)

  4. Result: The function returns initialTree, preserving the intercepted modal content instead of replacing it with __DEFAULT__

Why This Is Wrong for Redirects

The parallel route preservation logic makes sense for soft navigations (clicking links, using router.push), where users expect UI state to persist. But for server action redirects, the expectation is different:

  • The user explicitly triggered an action that redirects elsewhere
  • The redirect should behave more like a "hard" navigation
  • Intercepted route content (modals) should clear when redirecting away

The Fix

The fix cleans intercepted routes from the current tree when a redirect is detected before applying the patch during redirects. This allows the server's __DEFAULT__ segment to be properly applied, effectively closing the modal:

const treeToApplyPatchTo = redirectHref
  ? cleanInterceptedRoutesFromTree(currentTree)  // Remove intercepted routes first
  : currentTree

const newTree = applyRouterStatePatchToTree([''], treeToApplyPatchTo, treePatch, ...)

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants