rsnext/test/e2e/app-dir/shallow-routing/shallow-routing.test.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

496 lines
16 KiB
TypeScript
Raw Normal View History

Add experimental support for history.pushState and history.replaceState (#58335) ## 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
2023-11-13 14:32:08 +01:00
import { createNextDescribe } from 'e2e-utils'
import { check } from 'next-test-utils'
createNextDescribe(
'shallow-routing',
{
files: __dirname,
},
({ next }) => {
describe('pushState', () => {
it('should support setting data', async () => {
const browser = await next.browser('/a')
expect(
await browser
.elementByCss('#to-pushstate-data')
.click()
.waitForElementByCss('#pushstate-data')
.text()
).toBe('PushState Data')
await browser
.elementByCss('#push-state')
.click()
.waitForElementByCss('#state-updated')
.elementByCss('#get-latest')
.click()
await check(
() => browser.elementByCss('#my-data').text(),
`{"foo":"bar"}`
)
})
it('should support setting a different pathname reflected on usePathname', async () => {
const browser = await next.browser('/a')
expect(
await browser
.elementByCss('#to-pushstate-new-pathname')
.click()
.waitForElementByCss('#pushstate-pathname')
.text()
).toBe('PushState Pathname')
await browser.elementByCss('#push-pathname').click()
// Check usePathname value is the new pathname
await check(
() => browser.elementByCss('#my-data').text(),
'/my-non-existent-path'
)
// Check current url is the new pathname
expect(await browser.url()).toBe(`${next.url}/my-non-existent-path`)
})
it('should support setting a different searchParam reflected on useSearchParams', async () => {
const browser = await next.browser('/a')
expect(
await browser
.elementByCss('#to-pushstate-new-searchparams')
.click()
.waitForElementByCss('#pushstate-searchparams')
.text()
).toBe('PushState SearchParams')
await browser.elementByCss('#push-searchparams').click()
// Check useSearchParams value is the new searchparam
await check(() => browser.elementByCss('#my-data').text(), 'foo')
// Check current url is the new searchparams
expect(await browser.url()).toBe(
`${next.url}/pushstate-new-searchparams?query=foo`
)
// Same cycle a second time
await browser.elementByCss('#push-searchparams').click()
// Check useSearchParams value is the new searchparam
await check(() => browser.elementByCss('#my-data').text(), 'foo-added')
// Check current url is the new searchparams
expect(await browser.url()).toBe(
`${next.url}/pushstate-new-searchparams?query=foo-added`
)
})
Support passing a relative string to pushState/replaceState (#58438) Follow-up to #58335. Fixes https://github.com/vercel/next.js/discussions/48110#discussioncomment-7565394 As reported in the discussion passing a origin-relative string didn't work as `new URL` will throw an error. This ensures the `origin` parameter is provided to `new URL`. Added tests for the string behavior. <!-- Thanks for opening a PR! Your contribution is much appreciated. To make sure your PR is handled as smoothly as possible we request that you follow the checklist sections below. Choose the right checklist for the change(s) that you're making: ## For Contributors ### Improving Documentation - Run `pnpm prettier-fix` to fix formatting issues before opening the PR. - Read the Docs Contribution Guide to ensure your contribution follows the docs guidelines: https://nextjs.org/docs/community/contribution-guide ### Adding or Updating Examples - The "examples guidelines" are followed from our contributing doc https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md - Make sure the linting passes by running `pnpm build && pnpm lint`. See https://github.com/vercel/next.js/blob/canary/contributing/repository/linting.md ### Fixing a bug - Related issues linked using `fixes #number` - Tests added. See: https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs - Errors have a helpful link attached, see https://github.com/vercel/next.js/blob/canary/contributing.md ### Adding a feature - Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. (A discussion must be opened, see https://github.com/vercel/next.js/discussions/new?category=ideas) - Related issues/discussions are linked using `fixes #number` - e2e tests added (https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs) - Documentation added - Telemetry added. In case of a feature if it's used or not. - Errors have a helpful link attached, see https://github.com/vercel/next.js/blob/canary/contributing.md ## For Maintainers - Minimal description (aim for explaining to someone not on the team to understand the PR) - When linking to a Slack thread, you might want to share details of the conclusion - Link both the Linear (Fixes NEXT-xxx) and the GitHub issues - Add review comments if necessary to explain to the reviewer the logic behind a change ### What? ### Why? ### How? Closes NEXT- Fixes # --> --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> Co-authored-by: Zack Tanner <zacktanner@gmail.com>
2023-11-16 22:11:14 +01:00
it('should support setting a different url using a string', async () => {
const browser = await next.browser('/a')
expect(
await browser
.elementByCss('#to-pushstate-string-url')
.click()
.waitForElementByCss('#pushstate-string-url')
.text()
).toBe('PushState String Url')
await browser.elementByCss('#push-string-url').click()
// Check useSearchParams value is the new searchparam
await check(() => browser.elementByCss('#my-data').text(), 'foo')
// Check current url is the new searchparams
expect(await browser.url()).toBe(
`${next.url}/pushstate-string-url?query=foo`
)
// Same cycle a second time
await browser.elementByCss('#push-string-url').click()
// Check useSearchParams value is the new searchparam
await check(() => browser.elementByCss('#my-data').text(), 'foo-added')
// Check current url is the new searchparams
expect(await browser.url()).toBe(
`${next.url}/pushstate-string-url?query=foo-added`
)
})
it('should work when given a null state value', async () => {
const browser = await next.browser('/a')
expect(
await browser
.elementByCss('#to-pushstate-string-url')
.click()
.waitForElementByCss('#pushstate-string-url')
.text()
).toBe('PushState String Url')
await browser.elementByCss('#push-string-url-null').click()
// Check useSearchParams value is the new searchparam
await check(() => browser.elementByCss('#my-data').text(), 'foo')
// Check current url is the new searchparams
expect(await browser.url()).toBe(
`${next.url}/pushstate-string-url?query=foo`
)
// Same cycle a second time
await browser.elementByCss('#push-string-url-null').click()
// Check useSearchParams value is the new searchparam
await check(() => browser.elementByCss('#my-data').text(), 'foo-added')
// Check current url is the new searchparams
expect(await browser.url()).toBe(
`${next.url}/pushstate-string-url?query=foo-added`
)
})
})
it('should work when given an undefined state value', async () => {
const browser = await next.browser('/a')
expect(
await browser
.elementByCss('#to-pushstate-string-url')
.click()
.waitForElementByCss('#pushstate-string-url')
.text()
).toBe('PushState String Url')
await browser.elementByCss('#push-string-url-undefined').click()
// Check useSearchParams value is the new searchparam
await check(() => browser.elementByCss('#my-data').text(), 'foo')
// Check current url is the new searchparams
expect(await browser.url()).toBe(
`${next.url}/pushstate-string-url?query=foo`
)
// Same cycle a second time
await browser.elementByCss('#push-string-url-undefined').click()
// Check useSearchParams value is the new searchparam
await check(() => browser.elementByCss('#my-data').text(), 'foo-added')
// Check current url is the new searchparams
expect(await browser.url()).toBe(
`${next.url}/pushstate-string-url?query=foo-added`
)
Add experimental support for history.pushState and history.replaceState (#58335) ## 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
2023-11-13 14:32:08 +01:00
})
describe('replaceState', () => {
it('should support setting data', async () => {
const browser = await next.browser('/a')
expect(
await browser
.elementByCss('#to-replacestate-data')
.click()
.waitForElementByCss('#replacestate-data')
.text()
).toBe('ReplaceState Data')
await browser
.elementByCss('#replace-state')
.click()
.waitForElementByCss('#state-updated')
.elementByCss('#get-latest')
.click()
await check(
() => browser.elementByCss('#my-data').text(),
`{"foo":"bar"}`
)
})
it('should support setting a different pathname reflected on usePathname', async () => {
const browser = await next.browser('/a')
expect(
await browser
.elementByCss('#to-replacestate-new-pathname')
.click()
.waitForElementByCss('#replacestate-pathname')
.text()
).toBe('ReplaceState Pathname')
await browser.elementByCss('#replace-pathname').click()
// Check usePathname value is the new pathname
await check(
() => browser.elementByCss('#my-data').text(),
'/my-non-existent-path'
)
// Check current url is the new pathname
expect(await browser.url()).toBe(`${next.url}/my-non-existent-path`)
})
it('should support setting a different searchParam reflected on useSearchParams', async () => {
const browser = await next.browser('/a')
expect(
await browser
.elementByCss('#to-replacestate-new-searchparams')
.click()
.waitForElementByCss('#replacestate-searchparams')
.text()
).toBe('ReplaceState SearchParams')
await browser.elementByCss('#replace-searchparams').click()
// Check useSearchParams value is the new searchparam
await check(() => browser.elementByCss('#my-data').text(), 'foo')
// Check current url is the new searchparams
expect(await browser.url()).toBe(
`${next.url}/replacestate-new-searchparams?query=foo`
)
// Same cycle a second time
await browser.elementByCss('#replace-searchparams').click()
// Check useSearchParams value is the new searchparam
await check(() => browser.elementByCss('#my-data').text(), 'foo-added')
// Check current url is the new searchparams
expect(await browser.url()).toBe(
`${next.url}/replacestate-new-searchparams?query=foo-added`
)
})
Support passing a relative string to pushState/replaceState (#58438) Follow-up to #58335. Fixes https://github.com/vercel/next.js/discussions/48110#discussioncomment-7565394 As reported in the discussion passing a origin-relative string didn't work as `new URL` will throw an error. This ensures the `origin` parameter is provided to `new URL`. Added tests for the string behavior. <!-- Thanks for opening a PR! Your contribution is much appreciated. To make sure your PR is handled as smoothly as possible we request that you follow the checklist sections below. Choose the right checklist for the change(s) that you're making: ## For Contributors ### Improving Documentation - Run `pnpm prettier-fix` to fix formatting issues before opening the PR. - Read the Docs Contribution Guide to ensure your contribution follows the docs guidelines: https://nextjs.org/docs/community/contribution-guide ### Adding or Updating Examples - The "examples guidelines" are followed from our contributing doc https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md - Make sure the linting passes by running `pnpm build && pnpm lint`. See https://github.com/vercel/next.js/blob/canary/contributing/repository/linting.md ### Fixing a bug - Related issues linked using `fixes #number` - Tests added. See: https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs - Errors have a helpful link attached, see https://github.com/vercel/next.js/blob/canary/contributing.md ### Adding a feature - Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. (A discussion must be opened, see https://github.com/vercel/next.js/discussions/new?category=ideas) - Related issues/discussions are linked using `fixes #number` - e2e tests added (https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs) - Documentation added - Telemetry added. In case of a feature if it's used or not. - Errors have a helpful link attached, see https://github.com/vercel/next.js/blob/canary/contributing.md ## For Maintainers - Minimal description (aim for explaining to someone not on the team to understand the PR) - When linking to a Slack thread, you might want to share details of the conclusion - Link both the Linear (Fixes NEXT-xxx) and the GitHub issues - Add review comments if necessary to explain to the reviewer the logic behind a change ### What? ### Why? ### How? Closes NEXT- Fixes # --> --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> Co-authored-by: Zack Tanner <zacktanner@gmail.com>
2023-11-16 22:11:14 +01:00
it('should support setting a different url using a string', async () => {
const browser = await next.browser('/a')
expect(
await browser
.elementByCss('#to-replacestate-string-url')
.click()
.waitForElementByCss('#replacestate-string-url')
.text()
).toBe('ReplaceState String Url')
await browser.elementByCss('#replace-string-url').click()
// Check useSearchParams value is the new searchparam
await check(() => browser.elementByCss('#my-data').text(), 'foo')
// Check current url is the new searchparams
expect(await browser.url()).toBe(
`${next.url}/replacestate-string-url?query=foo`
)
// Same cycle a second time
await browser.elementByCss('#replace-string-url').click()
// Check useSearchParams value is the new searchparam
await check(() => browser.elementByCss('#my-data').text(), 'foo-added')
// Check current url is the new searchparams
expect(await browser.url()).toBe(
`${next.url}/replacestate-string-url?query=foo-added`
)
})
it('should work when given a null state value', async () => {
const browser = await next.browser('/a')
expect(
await browser
.elementByCss('#to-replacestate-string-url')
.click()
.waitForElementByCss('#replacestate-string-url')
.text()
).toBe('ReplaceState String Url')
await browser.elementByCss('#replace-string-url-null').click()
// Check useSearchParams value is the new searchparam
await check(() => browser.elementByCss('#my-data').text(), 'foo')
// Check current url is the new searchparams
expect(await browser.url()).toBe(
`${next.url}/replacestate-string-url?query=foo`
)
// Same cycle a second time
await browser.elementByCss('#replace-string-url-null').click()
// Check useSearchParams value is the new searchparam
await check(() => browser.elementByCss('#my-data').text(), 'foo-added')
// Check current url is the new searchparams
expect(await browser.url()).toBe(
`${next.url}/replacestate-string-url?query=foo-added`
)
})
it('should work when given an undefined state value', async () => {
const browser = await next.browser('/a')
expect(
await browser
.elementByCss('#to-replacestate-string-url')
.click()
.waitForElementByCss('#replacestate-string-url')
.text()
).toBe('ReplaceState String Url')
await browser.elementByCss('#replace-string-url-undefined').click()
// Check useSearchParams value is the new searchparam
await check(() => browser.elementByCss('#my-data').text(), 'foo')
// Check current url is the new searchparams
expect(await browser.url()).toBe(
`${next.url}/replacestate-string-url?query=foo`
)
// Same cycle a second time
await browser.elementByCss('#replace-string-url-undefined').click()
// Check useSearchParams value is the new searchparam
await check(() => browser.elementByCss('#my-data').text(), 'foo-added')
// Check current url is the new searchparams
expect(await browser.url()).toBe(
`${next.url}/replacestate-string-url?query=foo-added`
)
})
Add experimental support for history.pushState and history.replaceState (#58335) ## 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
2023-11-13 14:32:08 +01:00
})
describe('back and forward', () => {
describe('client-side navigation', () => {
it('should support setting a different pathname reflected on usePathname and then still support navigating back and forward', async () => {
const browser = await next.browser('/a')
expect(
await browser
.elementByCss('#to-pushstate-new-pathname')
.click()
.waitForElementByCss('#pushstate-pathname')
.text()
).toBe('PushState Pathname')
await browser.elementByCss('#push-pathname').click()
// Check usePathname value is the new pathname
await check(
() => browser.elementByCss('#my-data').text(),
'/my-non-existent-path'
)
// Check current url is the new pathname
expect(await browser.url()).toBe(`${next.url}/my-non-existent-path`)
// Navigate back
await browser.back()
// Check usePathname value is the old pathname
await check(
() => browser.elementByCss('#my-data').text(),
'/pushstate-new-pathname'
)
await browser.forward()
// Check usePathname value is the old pathname
await check(
() => browser.elementByCss('#my-data').text(),
'/my-non-existent-path'
)
})
})
// Browser navigation using `<a>` and such.
describe('mpa navigation', () => {
it('should support setting data and then still support navigating back and forward', async () => {
const browser = await next.browser('/a')
expect(
await browser
.elementByCss('#to-pushstate-data')
.click()
.waitForElementByCss('#pushstate-data')
.text()
).toBe('PushState Data')
await browser
.elementByCss('#push-state')
.click()
.waitForElementByCss('#state-updated')
.elementByCss('#get-latest')
.click()
await check(
() => browser.elementByCss('#my-data').text(),
`{"foo":"bar"}`
)
expect(
await browser
.elementByCss('#to-a-mpa')
.click()
.waitForElementByCss('#page-a')
.text()
).toBe('Page A')
// Navigate back
await browser.back()
// Check usePathname value is the old pathname
await check(
() => browser.elementByCss('#my-data').text(),
`{"foo":"bar"}`
)
await browser.forward()
await check(
() =>
browser
.elementByCss('#to-a-mpa')
.click()
.waitForElementByCss('#page-a')
.text(),
'Page A'
)
})
it('should support hash navigations while continuing to work for pushState/replaceState APIs', async () => {
const browser = await next.browser('/a')
expect(
await browser
.elementByCss('#to-pushstate-string-url')
.click()
.waitForElementByCss('#pushstate-string-url')
.text()
).toBe('PushState String Url')
await browser.elementByCss('#hash-navigation').click()
// Check current url contains the hash
expect(await browser.url()).toBe(
`${next.url}/pushstate-string-url#content`
)
await browser.elementByCss('#push-string-url').click()
// Check useSearchParams value is the new searchparam
await check(() => browser.elementByCss('#my-data').text(), 'foo')
// Check current url is the new searchparams
expect(await browser.url()).toBe(
`${next.url}/pushstate-string-url?query=foo`
)
// Same cycle a second time
await browser.elementByCss('#push-string-url').click()
// Check useSearchParams value is the new searchparam
await check(
() => browser.elementByCss('#my-data').text(),
'foo-added'
)
// Check current url is the new searchparams
expect(await browser.url()).toBe(
`${next.url}/pushstate-string-url?query=foo-added`
)
})
Add experimental support for history.pushState and history.replaceState (#58335) ## 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
2023-11-13 14:32:08 +01:00
})
})
}
)