nextjs - ✅(Solved) Fix Race condition in action queue can undo navigation [1 pull requests, 3 comments, 4 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
vercel/next.js#88343Fetched 2026-04-08 02:05:00
View on GitHub
Comments
3
Participants
4
Timeline
11
Reactions
0
Author
Timeline (top)
commented ×3labeled ×2subscribed ×2cross-referenced ×1

Fix Action

Fixed

PR fix notes

PR #88550: Fix/88343 server action race condition

Description (problem / solution / changelog)

What?

Prevents stale router state from being committed when an async server action resolves after a navigation has already committed a newer App Router state.

Why?

A race condition exists where an in-flight async server action can resolve after a client navigation (or restore) has already updated the router state. When this happens, the resolved server action result may overwrite the newer state, leading to incorrect navigation state and inconsistent UI behavior.

This is particularly visible when a router.refresh() occurs after navigation, as the refresh may replay a stale router tree produced by the late-resolving server action.

This scenario is described in #88343.

How?

  • Capture the router prevState before executing a server action in runAction
  • When handling the resolved result of an async server action:
    • Compare the current actionQueue.state against the captured prevState
    • If the state has changed (e.g. due to a navigation committing), skip applying the stale result
    • Continue processing the remaining action queue without mutating router state

This ensures navigations and refreshes always take precedence over outdated async server action results while preserving existing queue behavior.

Tests

Adds an App Router e2e test that reproduces the race condition:

  • Triggers an async server action without awaiting
  • Immediately navigates to a different route
  • Delays a router.refresh() on the destination route until after the server action resolves
  • Asserts that the refresh does not replay stale router state and the app remains on the correct route

The test fails without this change and passes with the fix applied.

Fixes #88343

Changed files

  • packages/next/src/client/components/app-router-instance.ts (modified, +11/-0)
  • test/e2e/app-dir/server-actions-navigation/app/actions.ts (added, +8/-0)
  • test/e2e/app-dir/server-actions-navigation/app/layout.tsx (added, +11/-0)
  • test/e2e/app-dir/server-actions-navigation/app/next/page.tsx (added, +30/-0)
  • test/e2e/app-dir/server-actions-navigation/app/page.tsx (added, +23/-0)
  • test/e2e/app-dir/server-actions-navigation/server-actions-navigation.test.ts (added, +32/-0)

Code Example

window.history.replaceState({}, "", url.toString());
callServerAction();
router.push("/other");
setTimeout(() => router.refresh(), 2_000);

---

Operating System:
  Platform: linux
  Arch: x64
  Version: #1 SMP PREEMPT_DYNAMIC Thu Jun  5 18:30:46 UTC 2025
  Available memory (MB): 32019
  Available CPU cores: 32
Binaries:
  Node: 24.11.1
  npm: 11.6.2
  Yarn: 1.22.21
  pnpm: 9.15.4
Relevant Packages:
  next: 16.1.1-canary.19 // Latest available version is detected (16.1.1-canary.19).
  eslint-config-next: N/A
  react: 19.2.3
  react-dom: 19.2.3
  typescript: 5.9.3
Next.js Config:
  output: N/A
RAW_BUFFERClick to expand / collapse

Link to the code that reproduces this issue

https://github.com/dmitrc/nextjs-action-queue-race/blob/main/app/ReproButton.tsx

To Reproduce

  1. Start the app in dev mode and navigate to /
  2. Click on the button
  3. Observe the navigation to /other page
  4. Observe the unexpected undo to / page shortly

Here is the screen recording -- https://imgur.com/a/asaleSm.

Hopefully console logs I am attaching below explain the issue in more depth.

Current vs. Expected behavior

The app performs the following steps (exaggerated to be in one place for the demo purposes):

window.history.replaceState({}, "", url.toString());
callServerAction();
router.push("/other");
setTimeout(() => router.refresh(), 2_000);

Expected behavior is navigation to /other page, and for the refresh to happen in its context.

Current behavior is navigation to /other page, followed by unexpected redirect back to / shortly.

What happens here is a bit tricky to explain, but the following breakpoints help:

  • app-router-instance.ts:102 (action running)
  • app-router-instance.ts:108 (action resolved, but discarded)
  • app-router-instance.ts:122 (action resolved, applying new state)
  • app-router-instance.ts:174 (action being enqueued)

With those, here is what we see in the logs:

<img width="1615" height="702" alt="Image" src="https://github.com/user-attachments/assets/637a7228-2dd0-45f1-addb-28d81b9332b4" />
  • restore gets enqueued and starts running (.pending)
  • server-action gets enqueued and put into .next
  • navigate gets enqueued, start running since hi-pri, and marks .pending to be discarded
  • restore finishes and gets discarded, runs server-action via runRemainingActions()
  • navigate finishes, sets the canonical URL in the state to /other
  • server-action finishes, ⚠️ sets the canonical URL in the state to /
    • This is the state that it started running with, but not up-to-date with latest navigation state
  • refresh gets enqueued, start running using latest canonical URL (which is /, wrongly)
  • RSC payload for / is delivered, page navigated back to / :(

Provide environment information

Operating System:
  Platform: linux
  Arch: x64
  Version: #1 SMP PREEMPT_DYNAMIC Thu Jun  5 18:30:46 UTC 2025
  Available memory (MB): 32019
  Available CPU cores: 32
Binaries:
  Node: 24.11.1
  npm: 11.6.2
  Yarn: 1.22.21
  pnpm: 9.15.4
Relevant Packages:
  next: 16.1.1-canary.19 // Latest available version is detected (16.1.1-canary.19).
  eslint-config-next: N/A
  react: 19.2.3
  react-dom: 19.2.3
  typescript: 5.9.3
Next.js Config:
  output: N/A

Which area(s) are affected? (Select all that apply)

Linking and Navigating

Which stage(s) are affected? (Select all that apply)

next dev (local), Other (Deployed), next start (local)

Additional context

No response

extent analysis

TL;DR

The issue can be fixed by ensuring that the server-action is aware of the latest navigation state before it sets the canonical URL.

Guidance

  • Review the callServerAction() function to see if it can be modified to account for the latest navigation state before setting the canonical URL.
  • Consider adding a check to ensure that the server-action is not using a stale state when setting the canonical URL.
  • Investigate the use of runRemainingActions() and how it affects the order of operations in the action queue.
  • Verify that the navigate action is correctly updating the canonical URL in the state before the server-action finishes.

Example

No code snippet is provided as the issue requires a deeper understanding of the specific implementation of callServerAction() and the action queue.

Notes

The issue seems to be related to the timing of the actions in the queue and how they interact with each other. The server-action is setting the canonical URL to / because it is using a stale state. To fix this, the server-action needs to be aware of the latest navigation state.

Recommendation

Apply a workaround to ensure that the server-action is using the latest navigation state before setting the canonical URL. This may involve modifying the callServerAction() function or the action queue to account for the latest state.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING