next.js
db4e6231 - Fix navigation getting reverted when a Server Action is in flight (#95391)

Commit
6 days ago
Fix navigation getting reverted when a Server Action is in flight (#95391) Alternative to https://github.com/vercel/next.js/pull/95188. Fixes https://github.com/vercel/next.js/issues/86151#issuecomment-4802297986. This fixes a bug where a navigation would spuriously get reverted once a Server Action settles. ## Test Plan New e2e tests that fail before the fix and pass after the fix. I've steered the agent to cover both the broken case now *and* the case that the regressing PR (#82674) was originally trying to fix. The test itself is reduced from https://github.com/vercel/next.js/pull/95188. Also, verified before/after in a real tiny app based on the tests. Before the fix, it reverts to the previous route; after the fix, it stays on the last route as it should. The tests themselves are a bit wonky and do some timing assertions. I had no luck reducing them further. ## Summary *Note: Everything below is written by Fable.* Fixes a regression where a navigation could get reverted shortly after it committed if a Server Action was still in flight when the navigation started: the destination page renders, then the router snaps back to the previous URL. Regressed in 15.5.1 via #82674. Reported in #86151, with a repro and initial investigation by @errmakov in #95188. ### Why? When a navigation starts while a Server Action is in flight, the router discards the action and the navigation takes its place at the head of the action queue. The discarded action still runs to completion. Since #82674, its completion also called `runRemainingActions`, which advances the queue — but the discarded action is no longer at the head of the queue, the navigation is. This started the next queued action while the navigation was still running, computed against router state that didn't include the navigation yet. Those actions send their requests with the previous URL, and applying their responses reverts the navigation. The promise rejection path had the same problem even before #82674: a discarded action that rejected would also advance the queue. ### How? Only the settlement of the action at the head of the queue may advance the queue. A discarded action's settlement (resolve or reject) now only records whether the action revalidated, and the deferred refresh is flushed once the queue goes idle. This keeps the behavior #82674 was after: no refresh for discarded actions that didn't revalidate, and a refresh for those that did — including when they settle mid-navigation, a case that could previously lose the refresh entirely. --------- Co-authored-by: errmakov <ide404@gmail.com>
Author
Parents
Loading