mirror of
https://github.com/hanzoai/next.js.git
synced 2026-10-30 14:46:38 +00:00
## What?
This PR introduces support for manually calling `history.pushState` and `history.replaceState`.
It's currently under an experimental flag:
```js
/**
* @type {import('next').NextConfig}
*/
const nextConfig = {
experimental: {
windowHistorySupport: true,
},
}
module.exports = nextConfig
```
Going forward I'll refer to `history.pushState` as `replaceState` is interchangable.
When the flag is enabled you're able to call the web platform `history.pushState` in the usual way:
```js
const data = {
foo: 'bar'
}
const url = '/my-new-url?search=tim'
window.history.pushState(data, '', url)
```
Let's start by explaining what would happen without the flag:
When a new history entry is pushed outside of the Next.js router any back navigation to that history entry will cause a browser reload as it can no longer be used by Next.js as the required metadata for the router is missing. In practice this makes it so that pushState/replaceState is not feasible to be used. Any pathname / searchParams added can't be observed by `usePathname` / `useSearchParams` either.
With the flag enabled the pushState/replaceState calls are instrumented and is synced into the Next.js router. This way the Next.js router's internal metadata is preserved, making back navigations apply still, and pathname / searchParams is synced as well, making sure that you can observe it using `usePathname` and `useSearchParams`.
## How?
- Added a new experimental flag `windowHistorySupport`
- Instruments `history.pushState` and `history.replaceState`
- Triggers the same action as popstate (ACTION_RESTORE) to sync the provided url (if provided) into the Next.js router
- Copies the Next.js values kept in history.state so that they are not lost
- Calls the original pushState/replaceState
~~Something to figure out is how we handle additional pushes/replaces in Next.js as that should override the history state that was previously set.~~
Went with this after discussing with @sebmarkbage:
- When you open a page it preserves the custom history state
- This is to solve this case: when you manually `window.history.pushState` / `window.history.replaceState` and then do an mpa navigation (i.e. `<a>` or `window.location.href`) and the navigate backwards the custom history state is preserved
- When you navigate back and forward (popstate) it preserves the custom history state
- When you navigate client-side (i.e. `router.push()` / `<Link>`) the custom history state is not preserved
35 lines
836 B
TypeScript
35 lines
836 B
TypeScript
'use client'
|
|
import { useEffect, useState } from 'react'
|
|
|
|
export default function Page() {
|
|
const [data, setData] = useState(null)
|
|
const [updated, setUpdated] = useState(false)
|
|
useEffect(() => {
|
|
setData(window.history.state.myData)
|
|
}, [])
|
|
return (
|
|
<>
|
|
<h1 id="replacestate-data">ReplaceState Data</h1>
|
|
{updated ? <div id="state-updated"></div> : null}
|
|
<pre id="my-data">{JSON.stringify(data)}</pre>
|
|
<button
|
|
onClick={() => {
|
|
setData(window.history.state.myData)
|
|
}}
|
|
id="get-latest"
|
|
>
|
|
Get latest data
|
|
</button>
|
|
<button
|
|
onClick={() => {
|
|
window.history.replaceState({ myData: { foo: 'bar' } }, '')
|
|
setUpdated(true)
|
|
}}
|
|
id="replace-state"
|
|
>
|
|
Replace state
|
|
</button>
|
|
</>
|
|
)
|
|
}
|