Skip to content

Commit 7a4a214

Browse files
committed
Pass loading boundary as prop to LayoutRouter
Removes the `loading` as a separate field in the server response payload. Instead, it is passed as a prop to LayoutRouter, similar to how the other boundary types (NotFound, et al) work. This simplifies much of the logic on the client because we no longer need to track `loading` separately from the rest of the segment data.
1 parent 6795538 commit 7a4a214

File tree

11 files changed

+114
-178
lines changed

11 files changed

+114
-178
lines changed

packages/next/src/client/components/app-router.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,7 @@ function Router({
424424
parentCacheNode: cache,
425425
parentSegmentPath: null,
426426
parentParams: {},
427+
parentLoadingData: null,
427428
// This is the <Activity> "name" that shows up in the Suspense DevTools.
428429
// It represents the root of the app.
429430
debugNameContext: '/',

packages/next/src/client/components/layout-router.tsx

Lines changed: 28 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ function InnerLayoutRouter({
277277
debugNameContext,
278278
cacheNode: maybeCacheNode,
279279
params,
280+
loading,
280281
url,
281282
isActive,
282283
}: {
@@ -285,6 +286,7 @@ function InnerLayoutRouter({
285286
debugNameContext: string
286287
cacheNode: CacheNode | null
287288
params: Params
289+
loading: LoadingModuleData | null
288290
url: string
289291
isActive: boolean
290292
}) {
@@ -378,6 +380,7 @@ function InnerLayoutRouter({
378380
parentCacheNode: cacheNode,
379381
parentSegmentPath: segmentPath,
380382
parentParams: params,
383+
parentLoadingData: loading,
381384
debugNameContext: debugNameContext,
382385

383386
// TODO-APP: overriding of url for parallel routes
@@ -402,33 +405,18 @@ function LoadingBoundary({
402405
children,
403406
}: {
404407
name: ActivityProps['name']
405-
loading: LoadingModuleData | Promise<LoadingModuleData>
408+
loading: LoadingModuleData | null
406409
children: React.ReactNode
407410
}): JSX.Element {
408-
// If loading is a promise, unwrap it. This happens in cases where we haven't
409-
// yet received the loading data from the server — which includes whether or
410-
// not this layout has a loading component at all.
411-
//
412-
// It's OK to suspend here instead of inside the fallback because this
413-
// promise will resolve simultaneously with the data for the segment itself.
414-
// So it will never suspend for longer than it would have if we didn't use
415-
// a Suspense fallback at all.
416-
let loadingModuleData
417-
if (
418-
typeof loading === 'object' &&
419-
loading !== null &&
420-
typeof (loading as any).then === 'function'
421-
) {
422-
const promiseForLoading = loading as Promise<LoadingModuleData>
423-
loadingModuleData = use(promiseForLoading)
424-
} else {
425-
loadingModuleData = loading as LoadingModuleData
426-
}
427-
428-
if (loadingModuleData) {
429-
const loadingRsc = loadingModuleData[0]
430-
const loadingStyles = loadingModuleData[1]
431-
const loadingScripts = loadingModuleData[2]
411+
// TODO: For LoadingBoundary, and the other built-in boundary types, don't
412+
// wrap in an extra function component if no user-defined boundary is
413+
// provided. In other words, inline this conditional wrapping logic into
414+
// the parent component. More efficient and keeps unnecessary junk out of
415+
// the component stack.
416+
if (loading !== null) {
417+
const loadingRsc = loading[0]
418+
const loadingStyles = loading[1]
419+
const loadingScripts = loading[2]
432420
return (
433421
<Suspense
434422
name={name}
@@ -460,6 +448,7 @@ export default function OuterLayoutRouter({
460448
templateStyles,
461449
templateScripts,
462450
template,
451+
loading,
463452
notFound,
464453
forbidden,
465454
unauthorized,
@@ -472,6 +461,7 @@ export default function OuterLayoutRouter({
472461
templateStyles: React.ReactNode | undefined
473462
templateScripts: React.ReactNode | undefined
474463
template: React.ReactNode
464+
loading: LoadingModuleData | null
475465
notFound: React.ReactNode | undefined
476466
forbidden: React.ReactNode | undefined
477467
unauthorized: React.ReactNode | undefined
@@ -487,6 +477,7 @@ export default function OuterLayoutRouter({
487477
parentCacheNode,
488478
parentSegmentPath,
489479
parentParams,
480+
parentLoadingData,
490481
url,
491482
isActive,
492483
debugNameContext,
@@ -616,15 +607,6 @@ export default function OuterLayoutRouter({
616607
const isVirtual = debugName === undefined
617608
const debugNameToDisplay = isVirtual ? undefined : debugNameContext
618609

619-
// TODO: The loading module data for a segment is stored on the parent, then
620-
// applied to each of that parent segment's parallel route slots. In the
621-
// simple case where there's only one parallel route (the `children` slot),
622-
// this is no different from if the loading module data where stored on the
623-
// child directly. But I'm not sure this actually makes sense when there are
624-
// multiple parallel routes. It's not a huge issue because you always have
625-
// the option to define a narrower loading boundary for a particular slot. But
626-
// this sort of smells like an implementation accident to me.
627-
const loadingModuleData = parentCacheNode.loading
628610
let child = (
629611
<TemplateContext.Provider
630612
key={stateKey}
@@ -637,7 +619,17 @@ export default function OuterLayoutRouter({
637619
>
638620
<LoadingBoundary
639621
name={debugNameToDisplay}
640-
loading={loadingModuleData}
622+
// TODO: The loading module data for a segment is stored on the
623+
// parent, then applied to each of that parent segment's
624+
// parallel route slots. In the simple case where there's only
625+
// one parallel route (the `children` slot), this is no
626+
// different from if the loading module data where stored on the
627+
// child directly. But I'm not sure this actually makes sense
628+
// when there are multiple parallel routes. It's not a huge
629+
// issue because you always have the option to define a narrower
630+
// loading boundary for a particular slot. But this sort of
631+
// smells like an implementation accident to me.
632+
loading={parentLoadingData}
641633
>
642634
<HTTPAccessFallbackBoundary
643635
notFound={notFound}
@@ -649,6 +641,7 @@ export default function OuterLayoutRouter({
649641
url={url}
650642
tree={tree}
651643
params={params}
644+
loading={loading}
652645
cacheNode={cacheNode}
653646
segmentPath={segmentPath}
654647
debugNameContext={childDebugNameContext}

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

Lines changed: 1 addition & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,7 @@ import type {
88
ChildSegmentMap,
99
CacheNode,
1010
} from '../../../shared/lib/app-router-types'
11-
import type {
12-
HeadData,
13-
LoadingModuleData,
14-
} from '../../../shared/lib/app-router-types'
11+
import type { HeadData } from '../../../shared/lib/app-router-types'
1512
import {
1613
PAGE_SEGMENT_KEY,
1714
DEFAULT_SEGMENT_KEY,
@@ -382,12 +379,10 @@ function updateCacheNodeOnNavigation(
382379
needsDynamicRequest = false
383380
} else {
384381
const seedRsc = seedData !== null ? seedData[0] : null
385-
const seedLoading = seedData !== null ? seedData[2] : null
386382
const result = createCacheNodeForSegment(
387383
navigatedAt,
388384
newRouteTree,
389385
seedRsc,
390-
seedLoading,
391386
newMetadataVaryPath,
392387
seedHead,
393388
newParallelRoutes,
@@ -719,12 +714,10 @@ function createCacheNodeOnNavigation(
719714
needsDynamicRequest = false
720715
} else {
721716
const seedRsc = seedData !== null ? seedData[0] : null
722-
const seedLoading = seedData !== null ? seedData[2] : null
723717
const result = createCacheNodeForSegment(
724718
navigatedAt,
725719
newRouteTree,
726720
seedRsc,
727-
seedLoading,
728721
newMetadataVaryPath,
729722
seedHead,
730723
newParallelRoutes,
@@ -992,7 +985,6 @@ function reuseDynamicCacheNode(
992985
dropPrefetchRsc ? null : existingCacheNode.prefetchRsc,
993986
existingCacheNode.head,
994987
dropPrefetchRsc ? null : existingCacheNode.prefetchHead,
995-
existingCacheNode.loading,
996988
parallelRoutes,
997989
existingCacheNode.navigatedAt
998990
)
@@ -1002,7 +994,6 @@ function createCacheNodeForSegment(
1002994
now: number,
1003995
tree: RouteTree,
1004996
seedRsc: React.ReactNode | null,
1005-
seedLoading: LoadingModuleData | Promise<LoadingModuleData> | null,
1006997
metadataVaryPath: PageVaryPath | null,
1007998
seedHead: HeadData | null,
1008999
newParallelRoutes: Map<string, ChildSegmentMap>,
@@ -1044,7 +1035,6 @@ function createCacheNodeForSegment(
10441035
null,
10451036
isPage ? seedHead : null,
10461037
null,
1047-
seedLoading,
10481038
newParallelRoutes,
10491039
now
10501040
),
@@ -1054,8 +1044,6 @@ function createCacheNodeForSegment(
10541044

10551045
let cachedRsc: React.ReactNode | null = null
10561046
let isCachedRscPartial: boolean = true
1057-
let cachedLoading: LoadingModuleData | Promise<LoadingModuleData> | null =
1058-
null
10591047

10601048
const segmentEntry = readSegmentCacheEntry(now, tree.varyPath)
10611049
if (segmentEntry !== null) {
@@ -1064,7 +1052,6 @@ function createCacheNodeForSegment(
10641052
// Happy path: a cache hit
10651053
cachedRsc = segmentEntry.rsc
10661054
isCachedRscPartial = segmentEntry.isPartial
1067-
cachedLoading = segmentEntry.loading
10681055
break
10691056
}
10701057
case EntryStatus.Pending: {
@@ -1086,9 +1073,6 @@ function createCacheNodeForSegment(
10861073
// we can assume the response will be full. This field is set to `false`
10871074
// for such segments.
10881075
isCachedRscPartial = segmentEntry.isPartial
1089-
cachedLoading = promiseForFulfilledEntry.then((entry) =>
1090-
entry !== null ? entry.loading : null
1091-
)
10921076
break
10931077
}
10941078
case EntryStatus.Empty:
@@ -1114,7 +1098,6 @@ function createCacheNodeForSegment(
11141098
// means the data failed to load; the LayoutRouter will suspend indefinitely
11151099
// until the router updates again (refer to finishNavigationTask).
11161100
let rsc: React.ReactNode | null
1117-
let loading: LoadingModuleData | Promise<LoadingModuleData> | null
11181101
let doesSegmentNeedDynamicRequest: boolean
11191102

11201103
if (seedRsc !== null) {
@@ -1124,15 +1107,13 @@ function createCacheNodeForSegment(
11241107
// partial cached state in the meantime.
11251108
prefetchRsc = cachedRsc
11261109
rsc = seedRsc
1127-
loading = seedLoading
11281110
} else {
11291111
// We already have a completely cached segment. Ignore the seed data,
11301112
// which may still be streaming in. This shouldn't happen in the normal
11311113
// case because the client will inform the server which segments are
11321114
// already fully cached, and the server will skip rendering them.
11331115
prefetchRsc = null
11341116
rsc = cachedRsc
1135-
loading = cachedLoading
11361117
}
11371118
doesSegmentNeedDynamicRequest = false
11381119
} else {
@@ -1145,16 +1126,10 @@ function createCacheNodeForSegment(
11451126
// data arrives from the server.
11461127
prefetchRsc = cachedRsc
11471128
rsc = createDeferredRsc()
1148-
// TODO: Technically, a loading boundary could contain dynamic data. We
1149-
// should have separate `loading` and `prefetchLoading` fields to handle
1150-
// this, like we do for the segment data and head.
1151-
const isCacheHit = cachedRsc !== null
1152-
loading = isCacheHit ? cachedLoading : createDeferredRsc()
11531129
} else {
11541130
// The data is fully cached.
11551131
prefetchRsc = null
11561132
rsc = cachedRsc
1157-
loading = cachedLoading
11581133
}
11591134
doesSegmentNeedDynamicRequest = isCachedRscPartial
11601135
}
@@ -1227,7 +1202,6 @@ function createCacheNodeForSegment(
12271202
prefetchRsc,
12281203
head,
12291204
prefetchHead,
1230-
loading,
12311205
newParallelRoutes,
12321206
now
12331207
),
@@ -1244,7 +1218,6 @@ function createCacheNode(
12441218
prefetchRsc: React.ReactNode | null,
12451219
head: React.ReactNode | null,
12461220
prefetchHead: HeadData | null,
1247-
loading: LoadingModuleData | Promise<LoadingModuleData> | null,
12481221
parallelRoutes: Map<string, ChildSegmentMap>,
12491222
navigatedAt: number
12501223
): CacheNode {
@@ -1253,7 +1226,6 @@ function createCacheNode(
12531226
prefetchRsc,
12541227
head,
12551228
prefetchHead,
1256-
loading,
12571229
parallelRoutes,
12581230
navigatedAt,
12591231
}
@@ -1702,14 +1674,6 @@ function finishPendingCacheNode(
17021674
// been populated by a different navigation. We must not overwrite it.
17031675
}
17041676

1705-
// If we navigated without a prefetch, then `loading` will be a deferred promise too.
1706-
// Fulfill it using the dynamic response so that we can display the loading boundary.
1707-
const loading = cacheNode.loading
1708-
if (isDeferredRsc(loading)) {
1709-
const dynamicLoading = dynamicData[2]
1710-
loading.resolve(dynamicLoading, debugInfo)
1711-
}
1712-
17131677
// Check if this is a leaf segment. If so, it will have a `head` property with
17141678
// a pending promise that needs to be resolved with the dynamic head from
17151679
// the server.
@@ -1796,11 +1760,6 @@ function abortPendingCacheNode(
17961760
}
17971761
}
17981762

1799-
const loading = cacheNode.loading
1800-
if (isDeferredRsc(loading)) {
1801-
loading.resolve(null, debugInfo)
1802-
}
1803-
18041763
// Check if this is a leaf segment. If so, it will have a `head` property with
18051764
// a pending promise that needs to be resolved. If an error was provided, we
18061765
// will not resolve it with an error, since this is rendered at the root of

packages/next/src/client/components/router-reducer/reducers/find-head-in-cache.test.tsx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ describe('findHeadInCache', () => {
3434
prefetchRsc: null,
3535
head: null,
3636
prefetchHead: null,
37-
loading: null,
3837
parallelRoutes: new Map([
3938
[
4039
'children',
@@ -47,7 +46,6 @@ describe('findHeadInCache', () => {
4746
prefetchRsc: null,
4847
head: null,
4948
prefetchHead: null,
50-
loading: null,
5149
parallelRoutes: new Map([
5250
[
5351
'children',
@@ -58,7 +56,6 @@ describe('findHeadInCache', () => {
5856
navigatedAt,
5957
head: null,
6058
prefetchHead: null,
61-
loading: null,
6259
parallelRoutes: new Map([
6360
[
6461
'children',
@@ -70,7 +67,6 @@ describe('findHeadInCache', () => {
7067
rsc: null,
7168
prefetchRsc: null,
7269
prefetchHead: null,
73-
loading: null,
7470
parallelRoutes: new Map(),
7571
head: null,
7672
},

0 commit comments

Comments
 (0)