Skip to content

Commit c81df11

Browse files
committed
strip search params from prerenders
1 parent f74a169 commit c81df11

File tree

4 files changed

+121
-2
lines changed

4 files changed

+121
-2
lines changed

packages/next/src/build/templates/app-page.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ export const __next_app__ = {
8383

8484
import * as entryBase from '../../server/app-render/entry-base' with { 'turbopack-transition': 'next-server-utility' }
8585
import { RedirectStatusCode } from '../../client/components/redirect-status-code'
86+
import { parseReqUrl } from '../../lib/url'
8687

8788
export * from '../../server/app-render/entry-base' with { 'turbopack-transition': 'next-server-utility' }
8889

@@ -424,8 +425,16 @@ export async function handler(
424425
*/
425426
fallbackRouteParams: FallbackRouteParams | null
426427
}): Promise<ResponseCacheEntry> => {
428+
const isRevalidate = isSSG && !postponed && !isDynamicRSCRequest
429+
let queryForRender = query
430+
if (isRevalidate) {
431+
// search params should never never be included in a prerender.
432+
// (this can happen if we're prerendering a page at runtime because of a request that included search params)
433+
queryForRender = {}
434+
req.url = req.url ? stripQueryParams(req.url) : undefined
435+
}
427436
const context: AppPageRouteHandlerContext = {
428-
query,
437+
query: queryForRender,
429438
params,
430439
page: normalizedSrcPage,
431440
sharedContext: {
@@ -461,7 +470,7 @@ export async function handler(
461470

462471
dir: routeModule.projectDir,
463472
isDraftMode,
464-
isRevalidate: isSSG && !postponed && !isDynamicRSCRequest,
473+
isRevalidate,
465474
botType,
466475
isOnDemandRevalidate,
467476
isPossibleServerAction,
@@ -1208,3 +1217,8 @@ export async function handler(
12081217
throw err
12091218
}
12101219
}
1220+
1221+
function stripQueryParams(rawUrl: string): string {
1222+
const parsedUrl = parseReqUrl(rawUrl)!
1223+
return `${parsedUrl.pathname}${parsedUrl.hash}`
1224+
}

test/e2e/app-dir/segment-cache/search-params/app/search-params/page.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,21 @@ export default function SearchParamsPage() {
3333
searchParam=d_full, prefetch=true
3434
</LinkAccordion>
3535
</li>
36+
<li>
37+
with route param
38+
<ul>
39+
<li>
40+
<LinkAccordion href="/search-params/target-page/one?searchParam=e_PPR">
41+
param=one, searchParam=e_PPR
42+
</LinkAccordion>
43+
</li>
44+
<li>
45+
<LinkAccordion href="/search-params/target-page/one?searchParam=f_PPR">
46+
param=one, searchParam=f_PPR
47+
</LinkAccordion>
48+
</li>
49+
</ul>
50+
</li>
3651
</ul>
3752
</>
3853
)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Suspense } from 'react'
2+
3+
async function SearchParam({ searchParams }) {
4+
const { searchParam } = await searchParams
5+
return `Search param: ${searchParam}`
6+
}
7+
8+
async function Param({ params }) {
9+
const { param } = await params
10+
return `Param: ${param}`
11+
}
12+
13+
export default async function Target({ params, searchParams }) {
14+
return (
15+
<>
16+
<Suspense fallback="Loading...">
17+
<div id="target-page-with-param">
18+
<Param params={params} />
19+
</div>
20+
</Suspense>
21+
<Suspense fallback="Loading...">
22+
<div id="target-page-with-search-param">
23+
<SearchParam searchParams={searchParams} />
24+
</div>
25+
</Suspense>
26+
</>
27+
)
28+
}

test/e2e/app-dir/segment-cache/search-params/segment-cache-search-params.test.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,68 @@ describe('segment cache (search params)', () => {
6666
expect(await result.innerText()).toBe('Search param: c_PPR')
6767
})
6868

69+
it('when prefetching a lazily generated ISR PPR page, does not include search params in the response', async () => {
70+
let act: ReturnType<typeof createRouterAct>
71+
const browser = await next.browser('/search-params', {
72+
beforePageLoad(page) {
73+
act = createRouterAct(page)
74+
},
75+
})
76+
77+
// Prefetch a page with param 'one' and search param `e_PPR`.
78+
const revealA = await browser.elementByCss(
79+
'input[data-link-accordion="/search-params/target-page/one?searchParam=e_PPR"]'
80+
)
81+
console.log('revealing link (e_PPR)')
82+
await act(
83+
async () => {
84+
await revealA.click()
85+
},
86+
// The response will include a shell of the page, but nothing that is
87+
// based on the search param.
88+
{
89+
includes: 'e_PPR',
90+
block: 'reject',
91+
}
92+
)
93+
94+
console.log('revealing link (f_PPR)')
95+
// Prefetch the same page but with the search param changed to `f_PPR`.
96+
const revealC = await browser.elementByCss(
97+
'input[data-link-accordion="/search-params/target-page/one?searchParam=f_PPR"]'
98+
)
99+
await act(
100+
async () => {
101+
await revealC.click()
102+
},
103+
// This should not issue a new request for the page segment, because
104+
// search params are not included in the the PPR shell. So we can reuse
105+
// the shell we fetched for `searchParam=a`.
106+
{ includes: 'target-page-with-search-param', block: 'reject' }
107+
)
108+
109+
console.log('navigating to link (f_PPR)')
110+
// Navigate to one of the links.
111+
const linkC = await browser.elementByCss(
112+
'a[href="/search-params/target-page/one?searchParam=f_PPR"]'
113+
)
114+
await act(
115+
async () => {
116+
await linkC.click()
117+
},
118+
// The search param streams in on navigation
119+
{
120+
includes: 'Search param: f_PPR',
121+
}
122+
)
123+
expect(await browser.elementById('target-page-with-param').text()).toBe(
124+
'Param: one'
125+
)
126+
expect(
127+
await browser.elementById('target-page-with-search-param').text()
128+
).toBe('Search param: f_PPR')
129+
})
130+
69131
it('when fetching without PPR (e.g. prefetch={true}), includes the search params in the cache key', async () => {
70132
let act: ReturnType<typeof createRouterAct>
71133
const browser = await next.browser('/search-params', {

0 commit comments

Comments
 (0)