Skip to content

Commit f7ec039

Browse files
committed
prevent duplicate RSC fetch when action redirects (#66620)
When checking which segment(s) need to be refreshed, we currently compare the current page URL with the segment's refresh marker. We should inspect the `mutable.canonicalUrl` value first since that's the URL we're changing to, followed by `state.canonicalUrl` as a fallback (indicating that there's no URL change pending). This is because the server action handler will receive a redirect URL prior to `location.pathname` being updated, so the router will incorrectly think it needs to refresh the data for the page we're going to. Closes NEXT-3500
1 parent dd6ab93 commit f7ec039

File tree

6 files changed

+49
-11
lines changed

6 files changed

+49
-11
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ export function refreshReducer(
122122
updatedTree: newTree,
123123
updatedCache: cache,
124124
includeNextUrl,
125+
canonicalUrl: mutable.canonicalUrl || state.canonicalUrl,
125126
})
126127

127128
mutable.cache = cache

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

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,11 @@ export function serverActionReducer(
210210
// Remove cache.data as it has been resolved at this point.
211211
mutable.inFlightServerAction = null
212212

213+
if (redirectLocation) {
214+
const newHref = createHrefFromUrl(redirectLocation, false)
215+
mutable.canonicalUrl = newHref
216+
}
217+
213218
for (const flightDataPath of flightData) {
214219
// FlightDataPath with more than two items means unexpected Flight data was returned
215220
if (flightDataPath.length !== 3) {
@@ -266,23 +271,17 @@ export function serverActionReducer(
266271
updatedTree: newTree,
267272
updatedCache: cache,
268273
includeNextUrl: Boolean(nextUrl),
274+
canonicalUrl: mutable.canonicalUrl || state.canonicalUrl,
269275
})
270276

271277
mutable.cache = cache
272278
mutable.prefetchCache = new Map()
273279
}
274280

275281
mutable.patchedTree = newTree
276-
mutable.canonicalUrl = href
277-
278282
currentTree = newTree
279283
}
280284

281-
if (redirectLocation) {
282-
const newHref = createHrefFromUrl(redirectLocation, false)
283-
mutable.canonicalUrl = newHref
284-
}
285-
286285
resolve(actionResult)
287286

288287
return handleMutable(state, mutable)

packages/next/src/client/components/router-reducer/refetch-inactive-parallel-segments.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ interface RefreshInactiveParallelSegments {
1010
updatedTree: FlightRouterState
1111
updatedCache: CacheNode
1212
includeNextUrl: boolean
13+
canonicalUrl: string
1314
}
1415

1516
/**
@@ -41,6 +42,7 @@ async function refreshInactiveParallelSegmentsImpl({
4142
includeNextUrl,
4243
fetchedSegments,
4344
rootTree = updatedTree,
45+
canonicalUrl,
4446
}: RefreshInactiveParallelSegments & {
4547
fetchedSegments: Set<string>
4648
rootTree: FlightRouterState
@@ -50,7 +52,7 @@ async function refreshInactiveParallelSegmentsImpl({
5052

5153
if (
5254
refetchPath &&
53-
refetchPath !== location.pathname + location.search &&
55+
refetchPath !== canonicalUrl &&
5456
refetchMarker === 'refresh' &&
5557
// it's possible for the tree to contain multiple segments that contain data at the same URL
5658
// we keep track of them so we can dedupe the requests
@@ -94,6 +96,7 @@ async function refreshInactiveParallelSegmentsImpl({
9496
includeNextUrl,
9597
fetchedSegments,
9698
rootTree,
99+
canonicalUrl,
97100
})
98101

99102
fetchPromises.push(parallelFetchPromise)

test/e2e/app-dir/parallel-routes-revalidation/app/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export default async function Home() {
88
).then((res) => res.text())
99

1010
return (
11-
<div>
11+
<div id="root-page">
1212
<Link href="/revalidate-modal">Open Revalidate Modal</Link>
1313
<Link href="/refresh-modal">Open Refresh Modal</Link>
1414
<Link href="/redirect-modal">Open Redirect Modal</Link>

test/e2e/app-dir/parallel-routes-revalidation/app/redirect/page.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ export default function Page() {
88
redirect('/')
99
}}
1010
>
11-
<button type="submit">Redirect</button>
11+
<button type="submit" id="redirect-page">
12+
Redirect
13+
</button>
1214
</form>
1315
)
1416
}

test/e2e/app-dir/parallel-routes-revalidation/parallel-routes-revalidation.test.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ createNextDescribe(
66
{
77
files: __dirname,
88
},
9-
({ next }) => {
9+
({ next, isNextStart }) => {
1010
it('should submit the action and revalidate the page data', async () => {
1111
const browser = await next.browser('/')
1212
await check(() => browser.hasElementByCssSelector('#create-entry'), false)
@@ -423,5 +423,38 @@ createNextDescribe(
423423
})
424424
})
425425
})
426+
427+
it('should not trigger a refresh for the page that is being redirected to', async () => {
428+
const rscRequests = []
429+
const prefetchRequests = []
430+
const browser = await next.browser('/redirect', {
431+
beforePageLoad(page) {
432+
page.on('request', async (req) => {
433+
const headers = await req.allHeaders()
434+
if (headers['rsc']) {
435+
const pathname = new URL(req.url()).pathname
436+
437+
if (headers['next-router-prefetch']) {
438+
prefetchRequests.push(pathname)
439+
} else {
440+
rscRequests.push(pathname)
441+
}
442+
}
443+
})
444+
},
445+
})
446+
447+
await browser.elementByCss('button').click()
448+
await browser.waitForElementByCss('#root-page')
449+
await browser.waitForIdleNetwork()
450+
451+
await retry(async () => {
452+
expect(rscRequests.length).toBe(0)
453+
454+
if (isNextStart) {
455+
expect(prefetchRequests.length).toBe(4)
456+
}
457+
})
458+
})
426459
}
427460
)

0 commit comments

Comments
 (0)