Upstream change
Next.js 1e113cf — "Fix stale data after navigation despite revalidation" (#95439)
The bug
The App Router action queue always updates React state in dispatch order. When a navigation displaces (preempts) a pending server action, the navigation's promise is the last one dispatched. So when the preempted action later runs — even if it revalidated data — its resulting state is no longer reflected on screen, because the navigation's state was published last. Result: the UI shows stale data after navigation despite a successful revalidation.
The fix
In packages/next/src/client/components/app-router-instance.ts:
- Add a
wasPreempted?: boolean flag to AppRouterActionQueue.
- When a pending action is discarded because a navigation takes priority (
actionQueue.pending.discarded = true), also set actionQueue.wasPreempted = true.
- In
runRemainingActions, once the queue is idle (pending === null) and wasPreempted was set, clear the flag and publish the final queue state: startTransition(() => setState(actionQueue.state)). The existing needsRefresh flush now runs alongside this.
Why it's relevant to vinext
vinext reimplements App Router client-side navigation and the server-actions dispatch queue. If vinext's action queue does not re-render the final state after a preempting navigation, users will see stale data after a navigation that interrupts a revalidating action — matching the upstream bug.
Action
- Check vinext's App Router action-queue implementation for equivalent preemption handling (navigation displacing a pending action).
- Ensure the final queue state is re-published once the queue drains after a preemption, so revalidated data is reflected.
- Port the regression test (
actions-discarded-navigation-revert) which was red before the fix.
References
Upstream change
Next.js 1e113cf — "Fix stale data after navigation despite revalidation" (#95439)
The bug
The App Router action queue always updates React state in dispatch order. When a navigation displaces (preempts) a pending server action, the navigation's promise is the last one dispatched. So when the preempted action later runs — even if it revalidated data — its resulting state is no longer reflected on screen, because the navigation's state was published last. Result: the UI shows stale data after navigation despite a successful revalidation.
The fix
In
packages/next/src/client/components/app-router-instance.ts:wasPreempted?: booleanflag toAppRouterActionQueue.actionQueue.pending.discarded = true), also setactionQueue.wasPreempted = true.runRemainingActions, once the queue is idle (pending === null) andwasPreemptedwas set, clear the flag and publish the final queue state:startTransition(() => setState(actionQueue.state)). The existingneedsRefreshflush now runs alongside this.Why it's relevant to vinext
vinext reimplements App Router client-side navigation and the server-actions dispatch queue. If vinext's action queue does not re-render the final state after a preempting navigation, users will see stale data after a navigation that interrupts a revalidating action — matching the upstream bug.
Action
actions-discarded-navigation-revert) which was red before the fix.References