next.js
ee7f8892 - Fix history push getting treated like replace when followed by refresh (#95392)

Commit
66 days ago
Fix history push getting treated like replace when followed by refresh (#95392) Currently this drops a history push: ```tsx router.push('/target') // this entry was getting lost router.refresh() ``` So you end up on `/target`, but the previous page's history entry was replaced instead of a new one being pushed. As a result, pressing Back yanks you past the previous page. It seems like a regression from #88046. I verified it's broken in 16.2, but it was working in 16.1. This fixes the regression. ## Test Plan New test fails before, works after. Also tried it in a small repro project. --- *Everything below is written by Claude.* The push happens when React commits a state whose `pushRef.pendingPush` is true (`HistoryUpdater`). Refresh-type actions (`refresh`, `hmr-refresh`, `server-patch`) create their state with a fresh `pushRef` where `pendingPush` is false. React can skip committing the superseded navigation state entirely, so `pendingPush: true` is never observed and the push never happens — `HistoryUpdater` replaces the previous page's history entry with the new URL instead of pushing a new one. Going back after that does nothing: the entry the browser returns to has no state, and the popstate handler ignores entries without state. The old router carried the previous state's `pendingPush` forward unless a reducer set it explicitly (https://github.com/vercel/next.js/blob/5ccc9078e0/packages/next/src/client/components/router-reducer/handle-mutable.ts#L41-L43), and the refresh reducer never set it. That guard was lost in #88046, which deleted `handleMutable` and moved the final state assembly into `completeSoftNavigation`, where `pushRef` is created fresh with `pendingPush: navigateType === 'push'`. This PR restores the old behavior there. There's no double push: if the navigation's state did commit, the push already happened and consumed the flag (`HistoryUpdater` mutates the `pushRef`), and its same-URL check turns a leftover flag into a replace anyway. In dev, an `hmr-refresh` dispatched while compiling the destination route on demand can supersede the navigation the same way. That's what's behind the recent `browser back to a revalidated page` flakes in `navigation.test.ts` (traced in #95385). <!-- NEXT_JS_LLM_PR -->
Author
Parents
Loading