Skip to content

Commit bf8cadd

Browse files
authored
[Segment Cache] Add refresh URL to reused default segments (#84627)
When segment is reused in the "default" parallel route slot, it is not part of the new page. So if the page is refreshed, we must fetch its data from the previous URL, not the new URL. To track this we add the previous URL to a special field in the FlightRouterState. Currently this field is added by addRefreshMarkerToActiveParallelSegments; this updates the "ppr-navigations" module to add it during the main traversal used by navigations, so we can eventually get rid addRefreshMarkerToActiveParallelSegments. Note that we don't need to add the marker to _all_ page segments, just the ones that are not part of the new page.
1 parent f90a780 commit bf8cadd

File tree

4 files changed

+50
-12
lines changed

4 files changed

+50
-12
lines changed

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

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type {
1515
} from '../../../shared/lib/app-router-types'
1616
import { DEFAULT_SEGMENT_KEY } from '../../../shared/lib/segment'
1717
import { matchSegment } from '../match-segments'
18+
import { createHrefFromUrl } from './create-href-from-url'
1819
import { createRouterCacheKey } from './create-router-cache-key'
1920
import type { FetchServerResponseResult } from './fetch-server-response'
2021
import { isNavigatingToNewRootLayout } from './is-navigating-to-new-root-layout'
@@ -91,6 +92,7 @@ export type Task = SPANavigationTask | MPANavigationTask
9192
// can be reused without initiating a server request.
9293
export function startPPRNavigation(
9394
navigatedAt: number,
95+
oldUrl: URL,
9496
oldCacheNode: CacheNode,
9597
oldRouterState: FlightRouterState,
9698
newRouterState: FlightRouterState,
@@ -103,6 +105,7 @@ export function startPPRNavigation(
103105
const segmentPath: Array<FlightSegmentPath> = []
104106
return updateCacheNodeOnNavigation(
105107
navigatedAt,
108+
oldUrl,
106109
oldCacheNode,
107110
oldRouterState,
108111
newRouterState,
@@ -118,6 +121,7 @@ export function startPPRNavigation(
118121

119122
function updateCacheNodeOnNavigation(
120123
navigatedAt: number,
124+
oldUrl: URL,
121125
oldCacheNode: CacheNode,
122126
oldRouterState: FlightRouterState,
123127
newRouterState: FlightRouterState,
@@ -230,7 +234,7 @@ function updateCacheNodeOnNavigation(
230234
// Reuse the existing Router State for this segment. We spawn a "task"
231235
// just to keep track of the updated router state; unlike most, it's
232236
// already fulfilled and won't be affected by the dynamic response.
233-
taskChild = spawnReusedTask(oldRouterStateChild)
237+
taskChild = reuseActiveSegmentInDefaultSlot(oldUrl, oldRouterStateChild)
234238
} else {
235239
// There's no currently active segment. Switch to the "create" path.
236240
taskChild = beginRenderingNewRouteTree(
@@ -299,6 +303,7 @@ function updateCacheNodeOnNavigation(
299303
// the children.
300304
taskChild = updateCacheNodeOnNavigation(
301305
navigatedAt,
306+
oldUrl,
302307
oldCacheNodeChild,
303308
oldRouterStateChild,
304309
newRouterStateChild,
@@ -726,9 +731,37 @@ function spawnPendingTask(
726731
return newTask
727732
}
728733

729-
function spawnReusedTask(reusedRouterState: FlightRouterState): Task {
730-
// Create a task that reuses an existing segment, e.g. when reusing
731-
// the current active segment in place of a default route.
734+
function reuseActiveSegmentInDefaultSlot(
735+
oldUrl: URL,
736+
oldRouterState: FlightRouterState
737+
): Task {
738+
// This is a "default" segment. These are never sent by the server during a
739+
// soft navigation; instead, the client reuses whatever segment was already
740+
// active in that slot on the previous route. This means if we later need to
741+
// refresh the segment, it will have to be refetched from the previous route's
742+
// URL. We store it in the Flight Router State.
743+
//
744+
// TODO: We also mark the segment with a "refresh" marker but I think we can
745+
// get rid of that eventually by making sure we only add URLs to page segments
746+
// that are reused. Then the presence of the URL alone is enough.
747+
let reusedRouterState
748+
749+
const oldRefreshMarker = oldRouterState[3]
750+
if (oldRefreshMarker === 'refresh') {
751+
// This segment was already reused from an even older route. Keep its
752+
// existing URL and refresh marker.
753+
reusedRouterState = oldRouterState
754+
} else {
755+
// This segment was not previously reused, and it's not on the new route.
756+
// So it must have been delivered in the old route.
757+
reusedRouterState = patchRouterStateWithNewChildren(
758+
oldRouterState,
759+
oldRouterState[1]
760+
)
761+
reusedRouterState[2] = createHrefFromUrl(oldUrl)
762+
reusedRouterState[3] = 'refresh'
763+
}
764+
732765
return {
733766
route: reusedRouterState,
734767
node: null,

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,8 +203,10 @@ export function navigateReducer(
203203
// Temporary glue code between the router reducer and the new navigation
204204
// implementation. Eventually we'll rewrite the router reducer to a
205205
// state machine.
206+
const currentUrl = new URL(state.canonicalUrl, location.origin)
206207
const result = navigateUsingSegmentCache(
207208
url,
209+
currentUrl,
208210
state.cache,
209211
state.tree,
210212
state.nextUrl,
@@ -267,13 +269,14 @@ export function navigateReducer(
267269
return handleExternalUrl(state, mutable, flightData, pendingPush)
268270
}
269271

272+
const oldCanonicalUrl = state.canonicalUrl
270273
const updatedCanonicalUrl = canonicalUrlOverride
271274
? createHrefFromUrl(canonicalUrlOverride)
272275
: href
273276

274277
const onlyHashChange =
275278
!!hash &&
276-
state.canonicalUrl.split('#', 1)[0] ===
279+
oldCanonicalUrl.split('#', 1)[0] ===
277280
updatedCanonicalUrl.split('#', 1)[0]
278281

279282
// If only the hash has changed, the server hasn't sent us any new data. We can just update
@@ -339,6 +342,7 @@ export function navigateReducer(
339342
) {
340343
const task = startPPRNavigation(
341344
navigatedAt,
345+
new URL(oldCanonicalUrl, url.origin),
342346
currentCache,
343347
currentTree,
344348
treePatch,

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ export type NavigationResult =
7777
*/
7878
export function navigate(
7979
url: URL,
80+
currentUrl: URL,
8081
currentCacheNode: CacheNode,
8182
currentFlightRouterState: FlightRouterState,
8283
nextUrl: string | null,
@@ -124,6 +125,7 @@ export function navigate(
124125
return navigateUsingPrefetchedRouteTree(
125126
now,
126127
url,
128+
currentUrl,
127129
nextUrl,
128130
isSamePageNavigation,
129131
currentCacheNode,
@@ -156,6 +158,7 @@ export function navigate(
156158
return navigateUsingPrefetchedRouteTree(
157159
now,
158160
url,
161+
currentUrl,
159162
nextUrl,
160163
isSamePageNavigation,
161164
currentCacheNode,
@@ -176,6 +179,7 @@ export function navigate(
176179
data: navigateDynamicallyWithNoPrefetch(
177180
now,
178181
url,
182+
currentUrl,
179183
nextUrl,
180184
isSamePageNavigation,
181185
currentCacheNode,
@@ -189,6 +193,7 @@ export function navigate(
189193
function navigateUsingPrefetchedRouteTree(
190194
now: number,
191195
url: URL,
196+
currentUrl: URL,
192197
nextUrl: string | null,
193198
isSamePageNavigation: boolean,
194199
currentCacheNode: CacheNode,
@@ -210,6 +215,7 @@ function navigateUsingPrefetchedRouteTree(
210215
const scrollableSegments: Array<FlightSegmentPath> = []
211216
const task = startPPRNavigation(
212217
now,
218+
currentUrl,
213219
currentCacheNode,
214220
currentFlightRouterState,
215221
prefetchFlightRouterState,
@@ -383,6 +389,7 @@ function readRenderSnapshotFromCache(
383389
async function navigateDynamicallyWithNoPrefetch(
384390
now: number,
385391
url: URL,
392+
currentUrl: URL,
386393
nextUrl: string | null,
387394
isSamePageNavigation: boolean,
388395
currentCacheNode: CacheNode,
@@ -442,6 +449,7 @@ async function navigateDynamicallyWithNoPrefetch(
442449
const scrollableSegments: Array<FlightSegmentPath> = []
443450
const task = startPPRNavigation(
444451
now,
452+
currentUrl,
445453
currentCacheNode,
446454
currentFlightRouterState,
447455
prefetchFlightRouterState,

test/client-segment-cache-tests-manifest.json

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,6 @@
1818
},
1919
"test/e2e/app-dir/parallel-routes-revalidation/parallel-routes-revalidation.test.ts": {
2020
"failed": [
21-
"parallel-routes-revalidation router.refresh (dynamic) - searchParams: false should correctly refresh data for previously intercepted modal and active page slot",
22-
"parallel-routes-revalidation router.refresh (dynamic) - searchParams: true should correctly refresh data for previously intercepted modal and active page slot",
23-
"parallel-routes-revalidation router.refresh (dynamic) - searchParams: true should correctly refresh data for the intercepted route and previously active page slot",
24-
"parallel-routes-revalidation router.refresh (regular) - searchParams: false should correctly refresh data for previously intercepted modal and active page slot",
25-
"parallel-routes-revalidation router.refresh (regular) - searchParams: true should correctly refresh data for previously intercepted modal and active page slot",
26-
"parallel-routes-revalidation router.refresh (regular) - searchParams: true should correctly refresh data for the intercepted route and previously active page slot",
27-
"parallel-routes-revalidation server action revalidation handles refreshing when multiple parallel slots are active",
2821
"parallel-routes-revalidation server action revalidation should not trigger a refresh for the page that is being redirected to"
2922
]
3023
},

0 commit comments

Comments
 (0)