nextjs - 💡(How to fix) Fix router.push inside useActionState action body breaks Back button when proxy.ts emits Set-Cookie (16.2.x regression) [1 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#93329Fetched 2026-04-29 06:10:42
View on GitHub
Comments
0
Participants
1
Timeline
6
Reactions
0
Participants
Timeline (top)
labeled ×3subscribed ×2issue_type_added ×1

Fix Action

Fix / Workaround

This is the same class of regression as #90513, partially fixed by #90533. The fix in #90533 does not cover the case where proxy.ts emits a Set-Cookie header and router.push is called from inside a useActionState action body — both of which are common patterns in production apps (any auth proxy, any cookie-based session, any "submit form then navigate" flow).

Code Example

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 25.3.0: Wed Jan 28 20:53:05 PST 2026; root:xnu-12377.81.4~5/RELEASE_ARM64_T6020
  Available memory (MB): 32768
  Available CPU cores: 12
Binaries:
  Node: 22.0.0
  npm: 10.5.1
  Yarn: 4.9.1
  pnpm: N/A
Relevant Packages:
  next: 16.2.3
  eslint-config-next: N/A
  react: 19.2.4
  react-dom: 19.2.4
  typescript: 5.9.3
Next.js Config:
  output: N/A

---

const [, formAction] = useActionState(async (prev, fd) => {
    const result = await someServerAction(prev, fd);   // await suspends the transition
    if (result?.redirectPath) router.push(result.redirectPath);  // runs while suspended
    return result;
 }, null);

return <form action={formAction}></form>;
RAW_BUFFERClick to expand / collapse

Link to the code that reproduces this issue

https://github.com/jncampbell/nextjs-repro-back-button-16.2

To Reproduce

  1. git clone https://github.com/jncampbell/nextjs-repro-back-button-16.2
  2. cd nextjs-repro-back-button-16.2
  3. npm install
  4. npm run build && npm start
  5. Open http://localhost:3000/account in a fresh browser tab (so the only entry in browser history is the new-tab page).
  6. Click Submit. URL changes to /dob.
  7. Click the browser Back button.

Current vs. Expected behavior

Expected: Back returns to /account.

Actual: Back lands on the new-tab page (the entry before /account). The /account history entry has been clobbered — replaceState was used instead of pushState when navigating to /dob.

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 25.3.0: Wed Jan 28 20:53:05 PST 2026; root:xnu-12377.81.4~5/RELEASE_ARM64_T6020
  Available memory (MB): 32768
  Available CPU cores: 12
Binaries:
  Node: 22.0.0
  npm: 10.5.1
  Yarn: 4.9.1
  pnpm: N/A
Relevant Packages:
  next: 16.2.3
  eslint-config-next: N/A
  react: 19.2.4
  react-dom: 19.2.4
  typescript: 5.9.3
Next.js Config:
  output: N/A

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

Linking and Navigating, Server Actions, Middleware

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

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

Additional context

This is the same class of regression as #90513, partially fixed by #90533. The fix in #90533 does not cover the case where proxy.ts emits a Set-Cookie header and router.push is called from inside a useActionState action body — both of which are common patterns in production apps (any auth proxy, any cookie-based session, any "submit form then navigate" flow).

The minimal failing form looks like:

const [, formAction] = useActionState(async (prev, fd) => {
    const result = await someServerAction(prev, fd);   // await suspends the transition
    if (result?.redirectPath) router.push(result.redirectPath);  // runs while suspended
    return result;
 }, null);

return <form action={formAction}>…</form>;

Tested against canary branch and can still reproduce

extent analysis

TL;DR

The issue can be fixed by using router.push with the replace option set to false to ensure pushState is used instead of replaceState when navigating to a new route.

Guidance

  • Review the code for instances where router.push is called from within a useActionState action body and ensure that the replace option is set to false.
  • Verify that the useActionState hook is not suspending the transition when calling router.push.
  • Check for any instances where proxy.ts emits a Set-Cookie header, which may be contributing to the issue.
  • Consider updating the code to handle the case where router.push is called from within a useActionState action body and a Set-Cookie header is emitted.

Example

const [, formAction] = useActionState(async (prev, fd) => {
  const result = await someServerAction(prev, fd);
  if (result?.redirectPath) router.push(result.redirectPath, undefined, { replace: false });
  return result;
}, null);

Notes

The issue appears to be related to a regression in Next.js, and the provided fix may not cover all cases. Further testing and verification are recommended to ensure the issue is fully resolved.

Recommendation

Apply workaround: use router.push with the replace option set to false to ensure pushState is used instead of replaceState when navigating to a new route. This should help mitigate the issue until a more comprehensive fix is available.

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