Skip to content

Commit 1125794

Browse files
committed
Pass RouteTree into navigation function
RouteTree is the client version of FlightRouterState. It's the same representation of the route tree, but it structured for optimized lookups of the client cache. The plan is the make this the primary/only type used for dealing with routes on the client; FlightRouterState will be used a transport format only. Using the same type accessing the cache during both prefetches and navigations makes it less likely for the behavior between the two paths to drift, too. Now that we've deleted the old, pre-Segment Cache navigation implementation, we should generally try to unify the data structures wherever possible.
1 parent 28d237c commit 1125794

File tree

12 files changed

+516
-483
lines changed

12 files changed

+516
-483
lines changed

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

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { extractPathFromFlightRouterState } from './compute-changed-path'
66
import type { AppRouterState } from './router-reducer-types'
77
import { getFlightDataPartsFromPath } from '../../flight-data-helpers'
88
import { createInitialCacheNodeForHydration } from './ppr-navigations'
9+
import { convertRootFlightRouterStateToRouteTree } from '../segment-cache/cache'
10+
import type { NormalizedSearch } from '../segment-cache/cache-key'
911

1012
export interface InitialRouterStateParameters {
1113
navigatedAt: number
@@ -44,14 +46,41 @@ export function createInitialRouterState({
4446
createHrefFromUrl(location)
4547
: initialCanonicalUrl
4648

49+
// Conver the initial FlightRouterState into the RouteTree type.
50+
// NOTE: The metadataVaryPath isn't used for anything currently because the
51+
// head is embedded into the CacheNode tree, but eventually we'll lift it out
52+
// and store it on the top-level state object.
53+
const acc = { metadataVaryPath: null }
54+
const initialRouteTree = convertRootFlightRouterStateToRouteTree(
55+
initialTree,
56+
initialRenderedSearch as NormalizedSearch,
57+
acc
58+
)
59+
const initialTask = createInitialCacheNodeForHydration(
60+
navigatedAt,
61+
initialRouteTree,
62+
initialSeedData,
63+
initialHead
64+
)
65+
66+
// NOTE: We intentionally don't check if any data needs to be fetched from the
67+
// server. We assume the initial hydration payload is sufficient to render
68+
// the page.
69+
//
70+
// The completeness of the initial data is an important property that we rely
71+
// on as a last-ditch mechanism for recovering the app; we must always be able
72+
// to reload a fresh HTML document to get to a consistent state.
73+
//
74+
// In the future, there may be cases where the server intentionally sends
75+
// partial data and expects the client to fill in the rest, in which case this
76+
// logic may change. (There already is a similar case where the server sends
77+
// _no_ hydration data in the HTML document at all, and the client fetches it
78+
// separately, but that's different because we still end up hydrating with a
79+
// complete tree.)
80+
4781
const initialState = {
48-
tree: initialTree,
49-
cache: createInitialCacheNodeForHydration(
50-
navigatedAt,
51-
initialTree,
52-
initialSeedData,
53-
initialHead
54-
),
82+
tree: initialTask.route,
83+
cache: initialTask.node,
5584
pushRef: {
5685
pendingPush: false,
5786
mpaNavigation: false,

packages/next/src/client/components/router-reducer/fetch-server-response.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,7 @@ import {
3535
} from '../../flight-data-helpers'
3636
import { getAppBuildId } from '../../app-build-id'
3737
import { setCacheBustingSearchParam } from './set-cache-busting-search-param'
38-
import {
39-
getRenderedSearch,
40-
urlToUrlWithoutFlightMarker,
41-
} from '../../route-params'
38+
import { urlToUrlWithoutFlightMarker } from '../../route-params'
4239
import type { NormalizedSearch } from '../segment-cache/cache-key'
4340
import { getDeploymentId } from '../../../shared/lib/deployment-id'
4441

@@ -256,7 +253,14 @@ export async function fetchServerResponse(
256253
return {
257254
flightData: normalizedFlightData,
258255
canonicalUrl: canonicalUrl,
259-
renderedSearch: getRenderedSearch(res),
256+
// TODO: We should be able to read this from the rewrite header, not the
257+
// Flight response. Theoretically they should always agree, but there are
258+
// currently some cases where it's incorrect for interception routes. We
259+
// can always trust the value in the response body. However, per-segment
260+
// prefetch responses don't embed the value in the body; they rely on the
261+
// header alone. So we need to investigate why the header is sometimes
262+
// wrong for interception routes.
263+
renderedSearch: flightResponse.q as NormalizedSearch,
260264
couldBeIntercepted: interception,
261265
prerendered: flightResponse.S,
262266
postponed,

packages/next/src/client/components/router-reducer/is-navigating-to-new-root-layout.test.ts

Lines changed: 0 additions & 100 deletions
This file was deleted.

packages/next/src/client/components/router-reducer/is-navigating-to-new-root-layout.ts

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import type { FlightRouterState } from '../../../shared/lib/app-router-types'
2+
import type { RouteTree } from '../segment-cache/cache'
23

34
export function isNavigatingToNewRootLayout(
45
currentTree: FlightRouterState,
5-
nextTree: FlightRouterState
6+
nextTree: RouteTree
67
): boolean {
78
// Compare segments
89
const currentTreeSegment = currentTree[0]
9-
const nextTreeSegment = nextTree[0]
10+
const nextTreeSegment = nextTree.segment
1011

1112
// If any segment is different before we find the root layout, the root layout has changed.
1213
// E.g. /same/(group1)/layout.js -> /same/(group2)/layout.js
@@ -27,17 +28,26 @@ export function isNavigatingToNewRootLayout(
2728
// Current tree root layout found
2829
if (currentTree[4]) {
2930
// If the next tree doesn't have the root layout flag, it must have changed.
30-
return !nextTree[4]
31+
return !nextTree.isRootLayout
3132
}
3233
// Current tree didn't have its root layout here, must have changed.
33-
if (nextTree[4]) {
34+
if (nextTree.isRootLayout) {
3435
return true
3536
}
36-
// We can't assume it's `parallelRoutes.children` here in case the root layout is `app/@something/layout.js`
37-
// But it's not possible to be more than one parallelRoutes before the root layout is found
38-
// TODO-APP: change to traverse all parallel routes
39-
const currentTreeChild = Object.values(currentTree[1])[0]
40-
const nextTreeChild = Object.values(nextTree[1])[0]
41-
if (!currentTreeChild || !nextTreeChild) return true
42-
return isNavigatingToNewRootLayout(currentTreeChild, nextTreeChild)
37+
38+
const slots = nextTree.slots
39+
const currentTreeChildren = currentTree[1]
40+
if (slots !== null) {
41+
for (const slot in slots) {
42+
const nextTreeChild = slots[slot]
43+
const currentTreeChild = currentTreeChildren[slot]
44+
if (
45+
currentTreeChild === undefined ||
46+
isNavigatingToNewRootLayout(currentTreeChild, nextTreeChild)
47+
) {
48+
return true
49+
}
50+
}
51+
}
52+
return true
4353
}

0 commit comments

Comments
 (0)