fix(next): initial prefetch cache not set properly with different search params (#65977)

cc @icyJoseph @ztanner

NOTE: The canary release
[`v14.1.1-canary.51`](https://github.com/vercel/next.js/releases/tag/v14.1.1-canary.51)
and below work as expected.

### Why?

Introduced from #61535, the initial prefetch cache is set based on the
`location.pathname`.
When a page is loaded WITH the search param, the cache key does not
contain information of the search param.

The issue is when on a dynamic page reading the `searchParams` value,
the value doesn't change if navigated as:

```
/?q=foo --> /
```

The prefetch cache hits, not re-rendering, and the `searchParams` value
is not passed properly.

### How?

For the prefetch cache, add the `location.search` as well.

Since `createPrefetchCacheKey` uses
[`createHrefFromUrl`](https://github.com/vercel/next.js/blob/canary/packages/next/src/client/components/router-reducer/create-href-from-url.ts)
which includes `location.search`, I'm expecting the change won't affect
current cache key behavior.

Fixes #64170
Fixes #65030

---------

Co-authored-by: Zack Tanner <1939140+ztanner@users.noreply.github.com>
This commit is contained in:
Jiwon Choi 2024-05-21 00:44:03 +09:00 committed by GitHub
parent 8a429e083e
commit 79c934aaec
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 53 additions and 1 deletions

View file

@ -101,7 +101,10 @@ export function createInitialRouterState({
// Seed the prefetch cache with this page's data.
// This is to prevent needlessly re-prefetching a page that is already reusable,
// and will avoid triggering a loading state/data fetch stall when navigating back to the page.
const url = new URL(location.pathname, location.origin)
const url = new URL(
`${location.pathname}${location.search}`,
location.origin
)
const initialFlightData: FlightData = [['', initialTree, null, null]]
createPrefetchCacheEntryForInitialLoad({

View file

@ -0,0 +1,7 @@
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html>
<body>{children}</body>
</html>
)
}

View file

@ -0,0 +1,11 @@
import Link from 'next/link'
export default function Page({ searchParams }: { searchParams: any }) {
return (
<>
<Link href="/">/</Link>
<Link href="/?q=bar">/?q=bar</Link>
<p>{JSON.stringify(searchParams)}</p>
</>
)
}

View file

@ -0,0 +1,6 @@
/**
* @type {import('next').NextConfig}
*/
const nextConfig = {}
module.exports = nextConfig

View file

@ -0,0 +1,25 @@
import { nextTestSetup } from 'e2e-utils'
import { retry } from 'next-test-utils'
describe('prefetch-searchparam', () => {
const { next } = nextTestSetup({
files: __dirname,
})
it('should set prefetch cache properly on different search params', async () => {
// load WITH search param
const browser = await next.browser('/?q=foo')
expect(await browser.elementByCss('p').text()).toBe('{"q":"foo"}')
// navigate to different search param, should update the search param
await browser.elementByCss('[href="/?q=bar"]').click()
await retry(async () => {
expect(await browser.elementByCss('p').text()).toBe('{"q":"bar"}')
})
// navigate to home, should clear the searchParams value
await browser.elementByCss('[href="/"]').click()
await retry(async () => {
expect(await browser.elementByCss('p').text()).toBe('{}')
})
})
})