mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
The route cache write was keying entries on `renderedSearch` — the
search string the server actually used to render the page — but lookups
use the requested URL's search component. These two are not equivalent:
for any search-invariant route (e.g. a static page), `renderedSearch` is
`''` regardless of what the request URL carried.
The result was that a request for `/a?foo=bar` against a static page
parked the cache entry at vary path `(/a, '', null)` — the same slot a
clean `/a` lookup keys to. The entry's stored `canonicalUrl` came from
the requesting URL (`/a?foo=bar`), so a later `router.push('/a')` or
`router.replace('/a')` would hit that slot and restore the stale search
string into the browser address bar, `window.location`, and
`useSearchParams()`.
The fix keys cache writes on the request URL's search, not
`renderedSearch`. Entries now sit at the slot keyed by the request that
wrote them, so a lookup for a different search variant doesn't find
them.
Regression test covers the reproduction from the user repo linked in the
bug reports.
Closes #91658
Closes #92187
<!-- NEXT_JS_LLM_PR -->
49 lines
1.7 KiB
TypeScript
49 lines
1.7 KiB
TypeScript
import { nextTestSetup } from 'e2e-utils'
|
|
import { retry } from 'next-test-utils'
|
|
|
|
describe('segment cache - stale search params on replace regression', () => {
|
|
const { next, isNextDev } = nextTestSetup({
|
|
files: __dirname,
|
|
})
|
|
|
|
if (isNextDev) {
|
|
test('skipped in dev mode', () => {})
|
|
return
|
|
}
|
|
|
|
// Regression test for https://github.com/vercel/next.js/issues/91658.
|
|
//
|
|
// Reproduction is taken directly from the user's report and from their
|
|
// minimal repro at https://github.com/dlehmhus/next-router-replace-bug:
|
|
//
|
|
// 1. Build and start the application.
|
|
// 2. Open `/?query=param` directly in the browser (full page load).
|
|
// 3. Click `<Link href="/dummy-page-1">` — navigates to /dummy-page-1.
|
|
// 4. Click `<Link href="/dummy-page-2">` — navigates to /dummy-page-2.
|
|
// 5. Click the button that calls `router.replace('/')`.
|
|
//
|
|
// Expected: URL is `/` with no search string.
|
|
// Buggy: URL becomes `/?query=param` — the search string from the
|
|
// original page load is restored.
|
|
|
|
it('router.replace to a clean URL clears the search params from the initial page load', async () => {
|
|
const browser = await next.browser('/?query=param')
|
|
await browser.waitForElementByCss('#home')
|
|
|
|
await browser.elementById('link-to-dummy-1').click()
|
|
await browser.waitForElementByCss('#dummy-page-1')
|
|
|
|
await browser.elementById('link-to-dummy-2').click()
|
|
await browser.waitForElementByCss('#dummy-page-2')
|
|
|
|
await browser.elementById('go-home').click()
|
|
await browser.waitForElementByCss('#home')
|
|
|
|
await retry(async () => {
|
|
const url = new URL(await browser.url())
|
|
expect(url.pathname).toBe('/')
|
|
expect(url.search).toBe('')
|
|
})
|
|
})
|
|
})
|