next.js icon indicating copy to clipboard operation
next.js copied to clipboard

[next/router on iOS 17+ and Chrome 127+] router.replace while clicking on a next link and going to the new page will make the user skip the previous page on back

Open aciulpan opened this issue 5 months ago • 8 comments

Link to the code that reproduces this issue

https://github.com/aciulpan/nextjs-router-replace-issue

To Reproduce

Requirements: You must be on iOS 17+ and Chrome 127+ in order to see the issue.

  1. Clone the repo
  2. npm run dev
  3. Start an ngrok tunnel or something so that you can access the page on a mobile phone (iOS 17+ and Chrome 127+), or simply deploy the app.
  4. Starting in the homepage, click on "PLP" page
  5. While in the "PLP" page, click on the "PDP" page. This will call a router.replace and put a timestamp in the query params
  6. Click back and see that you get sent back to the homepage instead of going back to the "PLP" page.

Current vs. Expected behavior

I expected that you be sent back to the previous page, but instead it skipped the "PLP" page entirely which means that the page was deleted from history. This does not happen on other browsers and devices as far as I can see.

Provide environment information

Note that this is not relevant to the issue since it depends on a combination of iOS and Chrome versions.


Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 24.6.0: Mon Jul 14 11:30:29 PDT 2025; root:xnu-11417.140.69~1/RELEASE_ARM64_T6000
  Available memory (MB): 32768
  Available CPU cores: 10
Binaries:
  Node: 24.0.1
  npm: 11.3.0
  Yarn: 1.22.22
  pnpm: N/A
Relevant Packages:
  next: 15.4.6 // Latest available version is detected (15.4.6).
  eslint-config-next: 15.4.6
  react: 19.1.0
  react-dom: 19.1.0
  typescript: 5.9.2
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), next build (local), next start (local), Vercel (Deployed)

Additional context

My original app runs on next 13 and I created a small app (that is linked here) in order to see if the upgrade to next 15 would fix my issue, but it does not.

aciulpan avatar Aug 11 '25 05:08 aciulpan

Hi - I am not following here, you call replace, which in turn invokes, https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState - and that modifies the History entry in place - which is why back now goes back home.

You'd need to use push, which invokes https://developer.mozilla.org/en-US/docs/Web/API/History/pushState

This is all documented in Pages Router isn't it?

I also think you need to preventDefault the input even when you do this kind of thing, cuz otherwise, next/links tries to do its own navigation, but your onClick is also triggering navigation itself.

icyJoseph avatar Aug 11 '25 08:08 icyJoseph

Hi - I am not following here, you call replace, which in turn invokes, https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState - and that modifies the History entry in place - which is why back now goes back home.

I don't understand why it would go back to home on just that combination of OS and browser? On the other browsers and OSes it works just fine.

You'd need to use push, which invokes https://developer.mozilla.org/en-US/docs/Web/API/History/pushState

This is not good for my use case, I need to be able to replace the url state with the query params that I want, for example I want to save the currently clicked card in a listing so that on back I can load the correct page and scroll to the card. At the same time I also do not want to steal the history stack from the user, which means that if they click back again they should be redirected to homepage instead of the same page but with different params. This feature is especially useful on long, paginated lists with infinite scroll.

I also think you need to preventDefault the input even when you do this kind of thing, cuz otherwise, next/links tries to do its own navigation, but your onClick is also triggering navigation itself.

I tried that as well (not in the linked project but in my own project) but to no avail, the same thing happened on the same device and OS

aciulpan avatar Aug 11 '25 12:08 aciulpan

Right, but then I am misunderstanding something. Let me try out a few things in your example. Could you take a screen-recording if possible?

icyJoseph avatar Aug 11 '25 13:08 icyJoseph

In this flow:

  • /
  • click product list page
    • :navigates to: /plp
    • history stack is now [/, plp]
  • click product details page
    • onClick handler ends up with replace to /plp?t=<timestamp>
    • router logic also tries to navigate with router.pushState internally but to /pdp

Now, you expect the history stack to be

  • [/, plp?t=<timestamp>, pdp]

But in the environment you point out the history stack is: [/, pdp] -

Could it be that there's a bug in that environment version with the History API?

I still think you can do e.preventDefault with this in the onClick - Of course we should investigate/triage this problem.

event.preventDefault()
await replace(/* plp with timestamp */)
await push(/* move to pdp */)

icyJoseph avatar Aug 11 '25 13:08 icyJoseph

Right, but then I am misunderstanding something. Let me try out a few things in your example. Could you take a screen-recording if possible?

Unfortunately I don't have an iOS, my colleague does but she's on holidays atm. We tested this on 2 different devices. We also have access to browser stack (neither chrome nor chromium are on 127+) and the mac simulator but I don't have access to chrome

aciulpan avatar Aug 11 '25 15:08 aciulpan

But in the environment you point out the history stack is: [/, pdp] -

This is correct. The PLP is gone completely.

Could it be that there's a bug in that environment version with the History API?

I still think you can do e.preventDefault with this in the onClick - Of course we should investigate/triage this problem.

event.preventDefault() await replace(/* plp with timestamp /) await push(/ move to pdp */)

I did try this as well on my main project and we ended up with the same results.

aciulpan avatar Aug 11 '25 15:08 aciulpan

This has also started happening for me on ios17+, on both Chrome and Firefox. For same firefox version but on ios 15 or 16 it does not happen. At this point I think it is iOS specific issue on the latest chrome and firefox versions..

kumailkhan1 avatar Aug 13 '25 09:08 kumailkhan1

Still present on iOS 26 and Chrome 143+. Navigation works fine on other platforms, but on iOS + Chrome using native browser gesture to go back, or browser native button to go back, behaviour is not consistent to say the least.

nenadVes avatar Dec 09 '25 11:12 nenadVes