Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type {
CacheNodeSeedData,
FlightRouterState,
FlightSegmentPath,
RefreshState,
} from '../../../shared/lib/app-router-types'
import type {
ChildSegmentMap,
Expand Down Expand Up @@ -51,7 +52,7 @@ export type NavigationTask = {
// The URL that should be used to fetch the dynamic data. This is only set
// when the segment cannot be refetched from the current route, because it's
// part of a "default" parallel slot that was reused during a navigation.
refreshUrl: string | null
refreshState: RefreshState | null
children: Map<string, NavigationTask> | null
}

Expand Down Expand Up @@ -175,6 +176,7 @@ export function createInitialCacheNodeForHydration(
export function startPPRNavigation(
navigatedAt: number,
oldUrl: URL,
oldRenderedSearch: string,
oldCacheNode: CacheNode | null,
oldRouterState: FlightRouterState,
newRouterState: FlightRouterState,
Expand All @@ -189,7 +191,11 @@ export function startPPRNavigation(
): NavigationTask | null {
const didFindRootLayout = false
const parentNeedsDynamicRequest = false
const parentRefreshUrl = null
const parentRefreshState = null
const oldRootRefreshState: RefreshState = [
createHrefFromUrl(oldUrl),
oldRenderedSearch,
]
return updateCacheNodeOnNavigation(
navigatedAt,
oldUrl,
Expand All @@ -207,7 +213,8 @@ export function startPPRNavigation(
null,
null,
parentNeedsDynamicRequest,
parentRefreshUrl,
oldRootRefreshState,
parentRefreshState,
accumulation
)
}
Expand All @@ -229,7 +236,8 @@ function updateCacheNodeOnNavigation(
parentSegmentPath: FlightSegmentPath | null,
parentParallelRouteKey: string | null,
parentNeedsDynamicRequest: boolean,
parentRefreshUrl: string | null,
oldRootRefreshState: RefreshState,
parentRefreshState: RefreshState | null,
accumulation: NavigationRequestAccumulation
): NavigationTask | null {
// Check if this segment matches the one in the previous route.
Expand Down Expand Up @@ -441,20 +449,20 @@ function updateCacheNodeOnNavigation(
// current route; it may have been reused from an older route. If so,
// we need to fetch its data from the old route's URL rather than current
// route's URL. Keep track of this as we traverse the tree.
const href = newRouterState[2]
const refreshUrl =
typeof href === 'string' && newRouterState[3] === 'refresh'
const maybeRefreshState = newRouterState[2]
const refreshState =
maybeRefreshState !== undefined && maybeRefreshState !== null
? // This segment is not present in the current route. Track its
// refresh URL as we continue traversing the tree.
href
maybeRefreshState
: // Inherit the refresh URL from the parent.
parentRefreshUrl
parentRefreshState

// If this segment itself needs to fetch new data from the server, then by
// definition it is being refreshed. Track its refresh URL so we know which
// URL to request the data from.
if (needsDynamicRequest && refreshUrl !== null) {
accumulateRefreshUrl(accumulation, refreshUrl)
if (needsDynamicRequest && refreshState !== null) {
accumulateRefreshUrl(accumulation, refreshState)
}

// As we diff the trees, we may sometimes modify (copy-on-write, not mutate)
Expand Down Expand Up @@ -524,7 +532,7 @@ function updateCacheNodeOnNavigation(
// a soft navigation; instead, the client reuses whatever segment was
// already active in that slot on the previous route.
newRouterStateChild = reuseActiveSegmentInDefaultSlot(
oldUrl,
oldRootRefreshState,
oldRouterStateChild
)
newSegmentChild = newRouterStateChild[0]
Expand Down Expand Up @@ -561,7 +569,8 @@ function updateCacheNodeOnNavigation(
segmentPath,
parallelRouteKey,
parentNeedsDynamicRequest || needsDynamicRequest,
refreshUrl,
oldRootRefreshState,
refreshState,
accumulation
)

Expand Down Expand Up @@ -618,7 +627,7 @@ function updateCacheNodeOnNavigation(
childNeedsDynamicRequest,
parentNeedsDynamicRequest
),
refreshUrl,
refreshState,
children: taskChildren,
}
}
Expand Down Expand Up @@ -923,7 +932,7 @@ function createCacheNodeOnNavigation(
),
// This route is not part of the current tree, so there's no reason to
// track the refresh URL.
refreshUrl: null,
refreshState: null,
children: taskChildren,
}
}
Expand Down Expand Up @@ -986,7 +995,7 @@ function createDynamicRequestTree(

function accumulateRefreshUrl(
accumulation: NavigationRequestAccumulation,
refreshUrl: string
refreshState: RefreshState
) {
// This is a refresh navigation, and we're inside a "default" slot that's
// not part of the current route; it was reused from an older route. In
Expand All @@ -998,6 +1007,7 @@ function accumulateRefreshUrl(
// we don't do it immediately here is so we can deduplicate multiple
// instances of the same URL into a single request. See
// listenForDynamicRequest for more details.
const refreshUrl = refreshState[0]
const separateRefreshUrls = accumulation.separateRefreshUrls
if (separateRefreshUrls === null) {
accumulation.separateRefreshUrls = new Set([refreshUrl])
Expand All @@ -1007,34 +1017,30 @@ function accumulateRefreshUrl(
}

function reuseActiveSegmentInDefaultSlot(
oldUrl: URL,
oldRootRefreshState: RefreshState,
oldRouterState: FlightRouterState
): FlightRouterState {
// This is a "default" segment. These are never sent by the server during a
// soft navigation; instead, the client reuses whatever segment was already
// active in that slot on the previous route. This means if we later need to
// refresh the segment, it will have to be refetched from the previous route's
// URL. We store it in the Flight Router State.
//
// TODO: We also mark the segment with a "refresh" marker but I think we can
// get rid of that eventually by making sure we only add URLs to page segments
// that are reused. Then the presence of the URL alone is enough.
let reusedRouterState

const oldRefreshMarker = oldRouterState[3]
if (oldRefreshMarker === 'refresh') {
const oldRefreshState = oldRouterState[2]
if (oldRefreshState !== undefined && oldRefreshState !== null) {
// This segment was already reused from an even older route. Keep its
// existing URL and refresh marker.
// existing URL and refresh state.
reusedRouterState = oldRouterState
} else {
// This segment was not previously reused, and it's not on the new route.
// So it must have been delivered in the old route.
// Since this route didn't already have a refresh state, it must have been
// reachable from the root of the old route. So we use the refresh state
// that represents the old route.
reusedRouterState = patchRouterStateWithNewChildren(
oldRouterState,
oldRouterState[1]
)
reusedRouterState[2] = createHrefFromUrl(oldUrl)
reusedRouterState[3] = 'refresh'
reusedRouterState[2] = oldRootRefreshState
}

return reusedRouterState
Expand Down Expand Up @@ -1631,7 +1637,7 @@ function abortRemainingPendingTasks(
//
// When this happens, we treat this the same as a refresh(). The entire
// tree will be re-rendered from the root.
if (task.refreshUrl === null) {
if (task.refreshState === null) {
// Trigger a "soft" refresh. Essentially the same as calling `refresh()`
// in a Server Action.
exitStatus = NavigationTaskExitStatus.SoftRetry
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,11 @@ export function navigateReducer(
// implementation. Eventually we'll rewrite the router reducer to a
// state machine.
const currentUrl = new URL(state.canonicalUrl, location.origin)
const currentRenderedSearch = state.renderedSearch
const result = navigateUsingSegmentCache(
url,
currentUrl,
currentRenderedSearch,
state.cache,
state.tree,
state.nextUrl,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export function refreshDynamicData(
// existing dynamic data (including in shared layouts) is re-fetched.
const currentCanonicalUrl = state.canonicalUrl
const currentUrl = new URL(currentCanonicalUrl, location.origin)
const currentRenderedSearch = state.renderedSearch
const currentFlightRouterState = state.tree
const shouldScroll = true

Expand All @@ -53,6 +54,7 @@ export function refreshDynamicData(
currentCanonicalUrl,
navigationSeed,
currentUrl,
currentRenderedSearch,
state.cache,
currentFlightRouterState,
freshnessPolicy,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export function restoreReducer(
const task = startPPRNavigation(
now,
currentUrl,
state.renderedSearch,
state.cache,
state.tree,
treeToRestore,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,7 @@ export function serverActionReducer(
// If there was no redirect, then the target URL is the same as the
// current URL.
const currentUrl = new URL(state.canonicalUrl, location.origin)
const currentRenderedSearch = state.renderedSearch
const redirectUrl =
redirectLocation !== undefined ? redirectLocation : currentUrl
const currentFlightRouterState = state.tree
Expand Down Expand Up @@ -425,6 +426,7 @@ export function serverActionReducer(
redirectCanonicalUrl,
navigationSeed,
currentUrl,
currentRenderedSearch,
state.cache,
currentFlightRouterState,
freshnessPolicy,
Expand All @@ -446,6 +448,7 @@ export function serverActionReducer(
const result = navigateUsingSegmentCache(
redirectUrl,
currentUrl,
currentRenderedSearch,
state.cache,
currentFlightRouterState,
nextUrl,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export function serverPatchReducer(
return handleExternalUrl(state, mutable, retryUrl.href, false)
}
const currentUrl = new URL(state.canonicalUrl, location.origin)
const currentRenderedSearch = state.renderedSearch
if (action.previousTree !== state.tree) {
// There was another, more recent navigation since the once that
// mismatched. We can abort the retry, but we still need to refresh the
Expand All @@ -50,6 +51,7 @@ export function serverPatchReducer(
retryCanonicalUrl,
retrySeed,
currentUrl,
currentRenderedSearch,
state.cache,
state.tree,
FreshnessPolicy.RefreshAll,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,17 @@ export type AppRouterState = {
* - This is the url you see in the browser.
*/
canonicalUrl: string

/**
* The search query observed by the server during rendering. This may be
* different from the canonical URL's search query if the server performed
* a rewrite. Even though a client component won't observe this (unless it
* were passed from a Server component), the client router needs to know this
* so it can properly cache segment data; it'ss part of a page segment's
* cache key.
*/
renderedSearch: string

/**
* The underlying "url" representing the UI state, which is used for intercepting routes.
*/
Expand Down
10 changes: 10 additions & 0 deletions packages/next/src/client/components/segment-cache/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export type NavigationResult =
export function navigate(
url: URL,
currentUrl: URL,
currentRenderedSearch: string,
currentCacheNode: CacheNode | null,
currentFlightRouterState: FlightRouterState,
nextUrl: string | null,
Expand Down Expand Up @@ -122,6 +123,7 @@ export function navigate(
now,
url,
currentUrl,
currentRenderedSearch,
nextUrl,
isSamePageNavigation,
currentCacheNode,
Expand Down Expand Up @@ -166,6 +168,7 @@ export function navigate(
now,
url,
currentUrl,
currentRenderedSearch,
nextUrl,
isSamePageNavigation,
currentCacheNode,
Expand Down Expand Up @@ -193,6 +196,7 @@ export function navigate(
now,
url,
currentUrl,
currentRenderedSearch,
nextUrl,
currentCacheNode,
currentFlightRouterState,
Expand All @@ -209,6 +213,7 @@ export function navigateToSeededRoute(
canonicalUrl: string,
navigationSeed: NavigationSeed,
currentUrl: URL,
currentRenderedSearch: string,
currentCacheNode: CacheNode | null,
currentFlightRouterState: FlightRouterState,
freshnessPolicy: FreshnessPolicy,
Expand All @@ -225,6 +230,7 @@ export function navigateToSeededRoute(
const task = startPPRNavigation(
now,
currentUrl,
currentRenderedSearch,
currentCacheNode,
currentFlightRouterState,
navigationSeed.tree,
Expand Down Expand Up @@ -259,6 +265,7 @@ function navigateUsingPrefetchedRouteTree(
now: number,
url: URL,
currentUrl: URL,
currentRenderedSearch: string,
nextUrl: string | null,
isSamePageNavigation: boolean,
currentCacheNode: CacheNode | null,
Expand Down Expand Up @@ -287,6 +294,7 @@ function navigateUsingPrefetchedRouteTree(
const task = startPPRNavigation(
now,
currentUrl,
currentRenderedSearch,
currentCacheNode,
currentFlightRouterState,
prefetchFlightRouterState,
Expand Down Expand Up @@ -483,6 +491,7 @@ async function navigateDynamicallyWithNoPrefetch(
now: number,
url: URL,
currentUrl: URL,
currentRenderedSearch: string,
nextUrl: string | null,
currentCacheNode: CacheNode | null,
currentFlightRouterState: FlightRouterState,
Expand Down Expand Up @@ -558,6 +567,7 @@ async function navigateDynamicallyWithNoPrefetch(
createHrefFromUrl(canonicalUrl),
navigationSeed,
currentUrl,
currentRenderedSearch,
currentCacheNode,
currentFlightRouterState,
freshnessPolicy,
Expand Down
Loading
Loading