Skip to content

Commit a7877d7

Browse files
authored
Bugfix: Incorrect search params stored in cache (vercel#94144)
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 vercel#91658 Closes vercel#92187 <!-- NEXT_JS_LLM_PR -->
1 parent 67d5f96 commit a7877d7

12 files changed

Lines changed: 136 additions & 2 deletions

File tree

packages/next/src/client/components/router-reducer/create-initial-router-state.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ export function createInitialRouterState({
106106
discoverKnownRoute(
107107
Date.now(),
108108
location.pathname,
109+
location.search as NormalizedSearch,
109110
null, // nextUrl — initial render is never an interception
110111
null, // No pending entry
111112
initialRouteTree,

packages/next/src/client/components/router-reducer/ppr-navigations.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1636,6 +1636,7 @@ function dispatchRetryDueToTreeMismatch(
16361636
discoverKnownRoute(
16371637
now,
16381638
retryUrl.pathname,
1639+
retryUrl.search as NormalizedSearch,
16391640
retryNextUrl,
16401641
null,
16411642
seed.routeTree,

packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,7 @@ export function serverActionReducer(
485485
discoverKnownRoute(
486486
now,
487487
redirectUrl.pathname,
488+
redirectUrl.search as NormalizedSearch,
488489
nextUrl,
489490
null, // No pending entry
490491
redirectSeed.routeTree,

packages/next/src/client/components/segment-cache/cache.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1141,6 +1141,7 @@ export function fulfillRouteCacheEntry(
11411141
export function writeRouteIntoCache(
11421142
now: number,
11431143
pathname: NormalizedPathname,
1144+
search: NormalizedSearch,
11441145
nextUrl: string | null,
11451146
tree: RouteTree,
11461147
metadataVaryPath: PageVaryPath,
@@ -1158,10 +1159,9 @@ export function writeRouteIntoCache(
11581159
canonicalUrl,
11591160
supportsPerSegmentPrefetching
11601161
)
1161-
const renderedSearch = fulfilledEntry.renderedSearch
11621162
const varyPath = getFulfilledRouteVaryPath(
11631163
pathname,
1164-
renderedSearch,
1164+
search,
11651165
nextUrl as NormalizedNextUrl | null,
11661166
couldBeIntercepted
11671167
)
@@ -1797,6 +1797,7 @@ export async function fetchRouteOnCacheMiss(
17971797
discoverKnownRoute(
17981798
Date.now(),
17991799
pathname,
1800+
search,
18001801
nextUrl,
18011802
entry,
18021803
routeTree,
@@ -1854,6 +1855,7 @@ export async function fetchRouteOnCacheMiss(
18541855
routeIsPPREnabled,
18551856
headVaryParams,
18561857
pathname,
1858+
search,
18571859
nextUrl
18581860
)
18591861
}
@@ -2347,6 +2349,7 @@ function writeDynamicTreeResponseIntoCache(
23472349
routeIsPPREnabled: boolean,
23482350
headVaryParams: VaryParams | null,
23492351
originalPathname: string,
2352+
originalSearch: NormalizedSearch,
23502353
nextUrl: string | null
23512354
): void {
23522355
const renderedSearch = getRenderedSearch(response)
@@ -2395,6 +2398,7 @@ function writeDynamicTreeResponseIntoCache(
23952398
discoverKnownRoute(
23962399
now,
23972400
originalPathname,
2401+
originalSearch,
23982402
nextUrl,
23992403
entry,
24002404
routeTree,

packages/next/src/client/components/segment-cache/navigation.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,7 @@ async function navigateToUnknownRoute(
437437
discoverKnownRoute(
438438
now,
439439
url.pathname,
440+
url.search as NormalizedSearch,
440441
nextUrl,
441442
null, // No pending entry
442443
navigationSeed.routeTree,

packages/next/src/client/components/segment-cache/optimistic-routes.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ let knownRouteTreeRoot: KnownRoutePart = createEmptyPart()
197197
export function discoverKnownRoute(
198198
now: number,
199199
pathname: string,
200+
search: NormalizedSearch,
200201
nextUrl: string | null,
201202
pendingEntry: PendingRouteCacheEntry | null,
202203
routeTree: RouteTree,
@@ -235,6 +236,7 @@ export function discoverKnownRoute(
235236
fulfilledEntry,
236237
now,
237238
pathname,
239+
search,
238240
nextUrl,
239241
tree,
240242
metadataVaryPath,
@@ -256,6 +258,7 @@ export function discoverKnownRoute(
256258
null,
257259
now,
258260
pathname,
261+
search,
259262
nextUrl,
260263
tree,
261264
metadataVaryPath,
@@ -276,6 +279,7 @@ function handleMismatchDueToRewrite(
276279
existingEntry: FulfilledRouteCacheEntry | null,
277280
now: number,
278281
pathname: string,
282+
search: NormalizedSearch,
279283
nextUrl: string | null,
280284
fullTree: RouteTree,
281285
metadataVaryPath: PageVaryPath,
@@ -289,6 +293,7 @@ function handleMismatchDueToRewrite(
289293
return writeRouteIntoCache(
290294
now,
291295
pathname as NormalizedPathname,
296+
search,
292297
nextUrl,
293298
fullTree,
294299
metadataVaryPath,
@@ -344,6 +349,7 @@ function discoverKnownRoutePart(
344349
// These are passed through unchanged for entry creation at the leaf
345350
now: number,
346351
pathname: string,
352+
search: NormalizedSearch,
347353
nextUrl: string | null,
348354
fullTree: RouteTree,
349355
metadataVaryPath: PageVaryPath,
@@ -370,6 +376,7 @@ function discoverKnownRoutePart(
370376
existingEntry,
371377
now,
372378
pathname,
379+
search,
373380
nextUrl,
374381
fullTree,
375382
metadataVaryPath,
@@ -409,6 +416,7 @@ function discoverKnownRoutePart(
409416
existingEntry,
410417
now,
411418
pathname,
419+
search,
412420
nextUrl,
413421
fullTree,
414422
metadataVaryPath,
@@ -429,6 +437,7 @@ function discoverKnownRoutePart(
429437
existingEntry,
430438
now,
431439
pathname,
440+
search,
432441
nextUrl,
433442
fullTree,
434443
metadataVaryPath,
@@ -496,6 +505,7 @@ function discoverKnownRoutePart(
496505
existingEntry,
497506
now,
498507
pathname,
508+
search,
499509
nextUrl,
500510
fullTree,
501511
metadataVaryPath,
@@ -517,6 +527,7 @@ function discoverKnownRoutePart(
517527
existingEntry,
518528
now,
519529
pathname,
530+
search,
520531
nextUrl,
521532
fullTree,
522533
metadataVaryPath,
@@ -534,6 +545,7 @@ function discoverKnownRoutePart(
534545
existingEntry,
535546
now,
536547
pathname,
548+
search,
537549
nextUrl,
538550
fullTree,
539551
metadataVaryPath,
@@ -565,6 +577,7 @@ function discoverKnownRoutePart(
565577
entry = writeRouteIntoCache(
566578
now,
567579
pathname as NormalizedPathname,
580+
search,
568581
nextUrl,
569582
fullTree,
570583
metadataVaryPath,
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import Link from 'next/link'
2+
3+
export default function Page() {
4+
return (
5+
<>
6+
<h1 id="dummy-page-1">Dummy Page 1</h1>
7+
<Link id="link-to-dummy-2" href="/dummy-page-2">
8+
Go to dummy page 2
9+
</Link>
10+
</>
11+
)
12+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
'use client'
2+
3+
import { useRouter } from 'next/navigation'
4+
5+
export default function Page() {
6+
const router = useRouter()
7+
return (
8+
<>
9+
<h1 id="dummy-page-2">Dummy Page 2</h1>
10+
<button id="go-home" onClick={() => router.replace('/')}>
11+
Go to home
12+
</button>
13+
</>
14+
)
15+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export default function RootLayout({
2+
children,
3+
}: Readonly<{
4+
children: React.ReactNode
5+
}>) {
6+
return (
7+
<html lang="en">
8+
<body>{children}</body>
9+
</html>
10+
)
11+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import Link from 'next/link'
2+
3+
export default function Home() {
4+
return (
5+
<>
6+
<h1 id="home">Home</h1>
7+
<Link id="link-to-dummy-1" href="/dummy-page-1">
8+
Go to dummy page 1
9+
</Link>
10+
</>
11+
)
12+
}

0 commit comments

Comments
 (0)