vercel / next.js

The React Framework
https://nextjs.org
MIT License
125k stars 26.7k forks source link

Programatically updating URL query parameters breaks back button #42804

Open smhutch opened 1 year ago

smhutch commented 1 year ago

Verify canary release

Provide environment information

Operating System:
  Platform: linux
  Arch: x64
  Version: #14 SMP Sat Jun 4 00:16:10 CEST 2022
Binaries:
  Node: 16.17.0
  npm: 8.15.0
  Yarn: 1.22.19
  pnpm: 7.1.0
Relevant packages:
  next: 13.0.0
  eslint-config-next: 12.0.9
  react: 18.2.0
  react-dom: 18.2.0

What browser are you using? (if relevant)

Firefox 106.0.5 (64-bit)

How are you deploying your application? (if relevant)

Vercel

Describe the Bug

  1. Create any Next.js page
  2. Somewhere on that page, update the query string (either using router.replace with shallow: false, or by using history.replaceState)
  3. Navigate to another page in the app (using client-side navigation via Link or router.push)
  4. Press back button → nothing happens

Expected Behavior

Back button works as normal

Link to reproduction - Issues with a link to complete (but minimal) reproduction code will be addressed faster

https://codesandbox.io/p/sandbox/inspiring-gagarin-40iqxd

To Reproduce

Navigate to this demo, then follow these steps:

CleanShot 2022-11-11 at 18 10 14

smhutch commented 1 year ago

After doing some more investigation, I managed to work around this issue by using this approach instead of router.replace():

const params = new URLSearchParams({ example: 'value' });
const path = window.location.pathname;
const pathWithQueryParams = `${path}?${params.toString()}`;

// This variable contains Next.js internals, and must be passed to `replaceState` for it to work properly.
const currentState = window.history.state;

history.replaceState(currentState, "", pathWithQueryParams)

Prototype showing this working.

smhutch commented 1 year ago

After some additional investigation, I landed on this solution:

  const params = new URLSearchParams({ example: 'value' });
  const path = window.location.pathname;
  const pathWithQueryParams = `${path}?${params.toString()}`;

  // It's vital to preserve the current history state, because it contains values that
  // Next.js uses to support client-side navigation via the back/forward buttons.
  const currentHistoryState = window.history.state;

  // Here we're manually overriding the current history state, which Next.js uses internally.
  // This is a bit of a high risk change, and should be revisited when upgrading Next.js.
  const newHistoryState = {
    ...currentHistoryState,
    as: path,
    url: path,
  };

  // Update query params, without triggering a reload
  history.replaceState(newHistoryState, '', path);