diff --git a/packages/next/src/client/components/app-router.tsx b/packages/next/src/client/components/app-router.tsx index 23c3ab55619aa5..47343002c0e58b 100644 --- a/packages/next/src/client/components/app-router.tsx +++ b/packages/next/src/client/components/app-router.tsx @@ -134,6 +134,7 @@ type AppRouterProps = Omit< > & { buildId: string initialHead: ReactNode + initialLayerAssets: ReactNode assetPrefix: string missingSlots: Set } @@ -183,6 +184,8 @@ export function createEmptyCacheNode(): CacheNode { rsc: null, prefetchRsc: null, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, parallelRoutes: new Map(), lazyDataResolved: false, @@ -307,6 +310,7 @@ function Router({ initialParallelRoutes, location: !isServer ? window.location : null, initialHead, + initialLayerAssets: null, couldBeIntercepted, }), [ @@ -645,6 +649,7 @@ function Router({ let content = ( {head} + {/* {cache.layerAssets} */} {cache.rsc} diff --git a/packages/next/src/client/components/layout-router.tsx b/packages/next/src/client/components/layout-router.tsx index cfa1f7c298b341..250b2954a8c7ca 100644 --- a/packages/next/src/client/components/layout-router.tsx +++ b/packages/next/src/client/components/layout-router.tsx @@ -355,6 +355,8 @@ function InnerLayoutRouter({ rsc: null, prefetchRsc: null, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, parallelRoutes: new Map(), lazyDataResolved: false, @@ -457,6 +459,7 @@ function InnerLayoutRouter({ loading: childNode.loading, }} > + {/* {childNode.layerAssets} */} {resolvedRsc} ) diff --git a/packages/next/src/client/components/router-reducer/apply-flight-data.ts b/packages/next/src/client/components/router-reducer/apply-flight-data.ts index b38e122e65939a..ccd516f999e282 100644 --- a/packages/next/src/client/components/router-reducer/apply-flight-data.ts +++ b/packages/next/src/client/components/router-reducer/apply-flight-data.ts @@ -11,14 +11,15 @@ export function applyFlightData( prefetchEntry?: PrefetchCacheEntry ): boolean { // The one before last item is the router state tree patch - const [treePatch, cacheNodeSeedData, head] = flightDataPath.slice(-3) + const [treePatch, cacheNodeSeedData, head, layerAssets] = + flightDataPath.slice(-4) // Handles case where prefetch only returns the router tree patch without rendered components. if (cacheNodeSeedData === null) { return false } - if (flightDataPath.length === 3) { + if (flightDataPath.length === 4) { const rsc = cacheNodeSeedData[2] const loading = cacheNodeSeedData[3] cache.loading = loading @@ -35,6 +36,7 @@ export function applyFlightData( treePatch, cacheNodeSeedData, head, + layerAssets, prefetchEntry ) } else { diff --git a/packages/next/src/client/components/router-reducer/clear-cache-node-data-for-segment-path.test.tsx b/packages/next/src/client/components/router-reducer/clear-cache-node-data-for-segment-path.test.tsx index abb2df3d6beabf..a68705e8291096 100644 --- a/packages/next/src/client/components/router-reducer/clear-cache-node-data-for-segment-path.test.tsx +++ b/packages/next/src/client/components/router-reducer/clear-cache-node-data-for-segment-path.test.tsx @@ -17,6 +17,8 @@ describe('clearCacheNodeDataForSegmentPath', () => { rsc: null, prefetchRsc: null, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, parallelRoutes: new Map(), lazyDataResolved: false, @@ -27,6 +29,8 @@ describe('clearCacheNodeDataForSegmentPath', () => { rsc: <>Root layout, prefetchRsc: null, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, lazyDataResolved: false, loading: null, @@ -42,6 +46,8 @@ describe('clearCacheNodeDataForSegmentPath', () => { prefetchRsc: null, lazyDataResolved: false, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, loading: null, parallelRoutes: new Map([ @@ -55,6 +61,8 @@ describe('clearCacheNodeDataForSegmentPath', () => { rsc: <>Page, prefetchRsc: null, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, parallelRoutes: new Map(), lazyDataResolved: false, @@ -76,6 +84,7 @@ describe('clearCacheNodeDataForSegmentPath', () => { expect(cache).toMatchInlineSnapshot(` { "head": null, + "layerAssets": null, "lazyData": null, "lazyDataResolved": false, "loading": null, @@ -83,6 +92,7 @@ describe('clearCacheNodeDataForSegmentPath', () => { "children" => Map { "linking" => { "head": null, + "layerAssets": null, "lazyData": null, "lazyDataResolved": false, "loading": null, @@ -90,11 +100,13 @@ describe('clearCacheNodeDataForSegmentPath', () => { "children" => Map { "" => { "head": null, + "layerAssets": null, "lazyData": null, "lazyDataResolved": false, "loading": null, "parallelRoutes": Map {}, "prefetchHead": null, + "prefetchLayerAssets": null, "prefetchRsc": null, "rsc": Page @@ -103,6 +115,7 @@ describe('clearCacheNodeDataForSegmentPath', () => { }, }, "prefetchHead": null, + "prefetchLayerAssets": null, "prefetchRsc": null, "rsc": Linking @@ -110,17 +123,20 @@ describe('clearCacheNodeDataForSegmentPath', () => { }, "dashboard" => { "head": null, + "layerAssets": null, "lazyData": null, "lazyDataResolved": false, "loading": null, "parallelRoutes": Map {}, "prefetchHead": null, + "prefetchLayerAssets": null, "prefetchRsc": null, "rsc": null, }, }, }, "prefetchHead": null, + "prefetchLayerAssets": null, "prefetchRsc": null, "rsc": null, } diff --git a/packages/next/src/client/components/router-reducer/clear-cache-node-data-for-segment-path.ts b/packages/next/src/client/components/router-reducer/clear-cache-node-data-for-segment-path.ts index 16ce5807e83d24..ae37b8a29021b9 100644 --- a/packages/next/src/client/components/router-reducer/clear-cache-node-data-for-segment-path.ts +++ b/packages/next/src/client/components/router-reducer/clear-cache-node-data-for-segment-path.ts @@ -40,6 +40,8 @@ export function clearCacheNodeDataForSegmentPath( rsc: null, prefetchRsc: null, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, parallelRoutes: new Map(), lazyDataResolved: false, @@ -57,6 +59,8 @@ export function clearCacheNodeDataForSegmentPath( rsc: null, prefetchRsc: null, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, parallelRoutes: new Map(), lazyDataResolved: false, @@ -72,6 +76,8 @@ export function clearCacheNodeDataForSegmentPath( rsc: childCacheNode.rsc, prefetchRsc: childCacheNode.prefetchRsc, head: childCacheNode.head, + layerAssets: childCacheNode.layerAssets, + prefetchLayerAssets: childCacheNode.prefetchLayerAssets, prefetchHead: childCacheNode.prefetchHead, parallelRoutes: new Map(childCacheNode.parallelRoutes), lazyDataResolved: childCacheNode.lazyDataResolved, diff --git a/packages/next/src/client/components/router-reducer/create-initial-router-state.test.tsx b/packages/next/src/client/components/router-reducer/create-initial-router-state.test.tsx index 46e99b41c7a95f..d4bf9981f09d6e 100644 --- a/packages/next/src/client/components/router-reducer/create-initial-router-state.test.tsx +++ b/packages/next/src/client/components/router-reducer/create-initial-router-state.test.tsx @@ -41,6 +41,7 @@ describe('createInitialRouterState', () => { initialParallelRoutes, location: new URL('/linking', 'https://localhost') as any, initialHead: Test, + initialLayerAssets: null, couldBeIntercepted: false, }) @@ -52,6 +53,7 @@ describe('createInitialRouterState', () => { initialParallelRoutes, location: new URL('/linking', 'https://localhost') as any, initialHead: Test, + initialLayerAssets: null, }) const expectedCache: CacheNode = { @@ -59,6 +61,8 @@ describe('createInitialRouterState', () => { rsc: children, prefetchRsc: null, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, lazyDataResolved: false, loading: null, @@ -82,6 +86,8 @@ describe('createInitialRouterState', () => { parallelRoutes: new Map(), loading: null, head: Test, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, lazyDataResolved: false, }, @@ -93,6 +99,8 @@ describe('createInitialRouterState', () => { rsc: null, prefetchRsc: null, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, lazyDataResolved: false, loading: null, diff --git a/packages/next/src/client/components/router-reducer/create-initial-router-state.ts b/packages/next/src/client/components/router-reducer/create-initial-router-state.ts index e3a2cbcdd832e9..b9c5368c6cd9c4 100644 --- a/packages/next/src/client/components/router-reducer/create-initial-router-state.ts +++ b/packages/next/src/client/components/router-reducer/create-initial-router-state.ts @@ -21,6 +21,7 @@ export interface InitialRouterStateParameters { initialParallelRoutes: CacheNode['parallelRoutes'] location: Location | null initialHead: ReactNode + initialLayerAssets: ReactNode couldBeIntercepted?: boolean } @@ -32,6 +33,7 @@ export function createInitialRouterState({ initialParallelRoutes, location, initialHead, + initialLayerAssets, couldBeIntercepted, }: InitialRouterStateParameters) { const isServer = !location @@ -42,6 +44,8 @@ export function createInitialRouterState({ rsc: rsc, prefetchRsc: null, head: null, + layerAssets: initialLayerAssets, + prefetchLayerAssets: null, prefetchHead: null, // The cache gets seeded during the first render. `initialParallelRoutes` ensures the cache from the first render is there during the second render. parallelRoutes: isServer ? new Map() : initialParallelRoutes, @@ -68,7 +72,8 @@ export function createInitialRouterState({ undefined, initialTree, initialSeedData, - initialHead + initialHead, + initialLayerAssets ) } @@ -106,7 +111,9 @@ export function createInitialRouterState({ location.origin ) - const initialFlightData: FlightData = [['', initialTree, null, null]] + const initialFlightData: FlightData = [ + ['', initialTree, null, null, initialLayerAssets], + ] createPrefetchCacheEntryForInitialLoad({ url, kind: PrefetchKind.AUTO, diff --git a/packages/next/src/client/components/router-reducer/fill-cache-with-new-subtree-data.test.tsx b/packages/next/src/client/components/router-reducer/fill-cache-with-new-subtree-data.test.tsx index 691def08dee51f..4fa13690afe4b9 100644 --- a/packages/next/src/client/components/router-reducer/fill-cache-with-new-subtree-data.test.tsx +++ b/packages/next/src/client/components/router-reducer/fill-cache-with-new-subtree-data.test.tsx @@ -31,6 +31,8 @@ describe('fillCacheWithNewSubtreeData', () => { rsc: null, prefetchRsc: null, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, loading: null, parallelRoutes: new Map(), @@ -41,6 +43,8 @@ describe('fillCacheWithNewSubtreeData', () => { rsc: <>Root layout, prefetchRsc: null, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, lazyDataResolved: false, loading: null, @@ -55,6 +59,8 @@ describe('fillCacheWithNewSubtreeData', () => { rsc: <>Linking, prefetchRsc: null, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, lazyDataResolved: false, loading: null, @@ -69,6 +75,8 @@ describe('fillCacheWithNewSubtreeData', () => { rsc: <>Page, prefetchRsc: null, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, lazyDataResolved: false, loading: null, @@ -101,6 +109,8 @@ describe('fillCacheWithNewSubtreeData', () => { rsc: null, prefetchRsc: null, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, lazyDataResolved: false, loading: null, @@ -115,6 +125,8 @@ describe('fillCacheWithNewSubtreeData', () => { rsc: <>Linking, prefetchRsc: null, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, lazyDataResolved: false, loading: null, @@ -130,6 +142,8 @@ describe('fillCacheWithNewSubtreeData', () => { rsc: <>Page, prefetchRsc: null, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, lazyDataResolved: false, loading: null, @@ -142,6 +156,8 @@ describe('fillCacheWithNewSubtreeData', () => { lazyData: null, lazyDataResolved: false, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, loading: null, parallelRoutes: new Map([ @@ -162,6 +178,8 @@ describe('fillCacheWithNewSubtreeData', () => { Head Injected! ), + layerAssets: null, + prefetchLayerAssets: null, }, ], ]), diff --git a/packages/next/src/client/components/router-reducer/fill-cache-with-new-subtree-data.ts b/packages/next/src/client/components/router-reducer/fill-cache-with-new-subtree-data.ts index 7a93f348595b6c..6468c922b4ecf6 100644 --- a/packages/next/src/client/components/router-reducer/fill-cache-with-new-subtree-data.ts +++ b/packages/next/src/client/components/router-reducer/fill-cache-with-new-subtree-data.ts @@ -17,7 +17,7 @@ export function fillCacheWithNewSubTreeData( flightDataPath: FlightDataPath, prefetchEntry?: PrefetchCacheEntry ): void { - const isLastEntry = flightDataPath.length <= 5 + const isLastEntry = flightDataPath.length <= 6 const [parallelRouteKey, segment] = flightDataPath const cacheKey = createRouterCacheKey(segment) @@ -47,6 +47,7 @@ export function fillCacheWithNewSubTreeData( childCacheNode === existingChildCacheNode ) { const seedData: CacheNodeSeedData = flightDataPath[3] + const layerAssets = flightDataPath[5] const rsc = seedData[2] const loading = seedData[3] childCacheNode = { @@ -54,6 +55,8 @@ export function fillCacheWithNewSubTreeData( rsc, prefetchRsc: null, head: null, + layerAssets, + prefetchLayerAssets: null, prefetchHead: null, loading, // Ensure segments other than the one we got data for are preserved. @@ -77,6 +80,7 @@ export function fillCacheWithNewSubTreeData( flightDataPath[2], seedData, flightDataPath[4], + flightDataPath[5], prefetchEntry ) @@ -97,6 +101,8 @@ export function fillCacheWithNewSubTreeData( rsc: childCacheNode.rsc, prefetchRsc: childCacheNode.prefetchRsc, head: childCacheNode.head, + layerAssets: childCacheNode.layerAssets, + prefetchLayerAssets: childCacheNode.prefetchLayerAssets, prefetchHead: childCacheNode.prefetchHead, parallelRoutes: new Map(childCacheNode.parallelRoutes), lazyDataResolved: false, diff --git a/packages/next/src/client/components/router-reducer/fill-lazy-items-till-leaf-with-head.test.tsx b/packages/next/src/client/components/router-reducer/fill-lazy-items-till-leaf-with-head.test.tsx index 226fb3bd0af2ce..a2c9517bd4ef07 100644 --- a/packages/next/src/client/components/router-reducer/fill-lazy-items-till-leaf-with-head.test.tsx +++ b/packages/next/src/client/components/router-reducer/fill-lazy-items-till-leaf-with-head.test.tsx @@ -40,6 +40,8 @@ describe('fillLazyItemsTillLeafWithHead', () => { rsc: null, prefetchRsc: null, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, parallelRoutes: new Map(), lazyDataResolved: false, @@ -50,6 +52,8 @@ describe('fillLazyItemsTillLeafWithHead', () => { rsc: <>Root layout, prefetchRsc: null, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, lazyDataResolved: false, loading: null, @@ -64,6 +68,8 @@ describe('fillLazyItemsTillLeafWithHead', () => { rsc: <>Linking, prefetchRsc: null, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, lazyDataResolved: false, loading: null, @@ -78,6 +84,8 @@ describe('fillLazyItemsTillLeafWithHead', () => { rsc: <>Page, prefetchRsc: null, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, lazyDataResolved: false, loading: null, @@ -108,7 +116,8 @@ describe('fillLazyItemsTillLeafWithHead', () => { existingCache, treePatch, cacheNodeSeedData, - head + head, + null ) const expectedCache: CacheNode = { @@ -116,6 +125,8 @@ describe('fillLazyItemsTillLeafWithHead', () => { rsc: null, prefetchRsc: null, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, lazyDataResolved: false, loading: null, @@ -130,6 +141,8 @@ describe('fillLazyItemsTillLeafWithHead', () => { rsc: null, prefetchRsc: null, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, lazyDataResolved: false, loading: null, @@ -154,6 +167,7 @@ describe('fillLazyItemsTillLeafWithHead', () => { rsc: null, prefetchRsc: null, prefetchHead: null, + prefetchLayerAssets: null, loading: null, parallelRoutes: new Map(), lazyDataResolved: false, @@ -162,6 +176,7 @@ describe('fillLazyItemsTillLeafWithHead', () => { About page! ), + layerAssets: null, }, ], ]), @@ -170,6 +185,8 @@ describe('fillLazyItemsTillLeafWithHead', () => { rsc: null, prefetchRsc: null, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, }, ], @@ -180,6 +197,8 @@ describe('fillLazyItemsTillLeafWithHead', () => { rsc: <>Page, prefetchRsc: null, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, loading: null, parallelRoutes: new Map(), diff --git a/packages/next/src/client/components/router-reducer/fill-lazy-items-till-leaf-with-head.ts b/packages/next/src/client/components/router-reducer/fill-lazy-items-till-leaf-with-head.ts index e375b22d0f3774..7f9cbe0f81c365 100644 --- a/packages/next/src/client/components/router-reducer/fill-lazy-items-till-leaf-with-head.ts +++ b/packages/next/src/client/components/router-reducer/fill-lazy-items-till-leaf-with-head.ts @@ -15,6 +15,7 @@ export function fillLazyItemsTillLeafWithHead( routerState: FlightRouterState, cacheNodeSeedData: CacheNodeSeedData | null, head: React.ReactNode, + layerAssets: React.ReactNode, prefetchEntry?: PrefetchCacheEntry ): void { const isLastSegment = Object.keys(routerState[1]).length === 0 @@ -60,13 +61,15 @@ export function fillLazyItemsTillLeafWithHead( newCacheNode = { lazyData: null, rsc: seedNode, - // This is a PPR-only field. When PPR is enabled, we shouldn't hit + // The prefetch prefixed fields are PPR-only. When PPR is enabled, we shouldn't hit // this path during a navigation, but until PPR is fully implemented // yet it's possible the existing node does have a non-null // `prefetchRsc`. As an incremental step, we'll just de-opt to the // old behavior — no PPR value. prefetchRsc: null, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, loading, parallelRoutes: new Map(existingCacheNode?.parallelRoutes), @@ -83,6 +86,8 @@ export function fillLazyItemsTillLeafWithHead( // PPR value, if it exists. prefetchRsc: existingCacheNode.prefetchRsc, head: existingCacheNode.head, + layerAssets: existingCache.layerAssets, + prefetchLayerAssets: existingCache.prefetchLayerAssets, prefetchHead: existingCacheNode.prefetchHead, parallelRoutes: new Map(existingCacheNode.parallelRoutes), lazyDataResolved: existingCacheNode.lazyDataResolved, @@ -96,6 +101,8 @@ export function fillLazyItemsTillLeafWithHead( rsc: null, prefetchRsc: null, head: null, + prefetchLayerAssets: null, + layerAssets: null, prefetchHead: null, parallelRoutes: new Map(existingCacheNode?.parallelRoutes), lazyDataResolved: false, @@ -112,6 +119,7 @@ export function fillLazyItemsTillLeafWithHead( parallelRouteState, parallelSeedData ? parallelSeedData : null, head, + layerAssets, prefetchEntry ) @@ -130,6 +138,8 @@ export function fillLazyItemsTillLeafWithHead( rsc: seedNode, prefetchRsc: null, head: null, + prefetchLayerAssets: null, + layerAssets: null, prefetchHead: null, parallelRoutes: new Map(), lazyDataResolved: false, @@ -143,6 +153,8 @@ export function fillLazyItemsTillLeafWithHead( rsc: null, prefetchRsc: null, head: null, + prefetchLayerAssets: null, + layerAssets: null, prefetchHead: null, parallelRoutes: new Map(), lazyDataResolved: false, @@ -163,6 +175,7 @@ export function fillLazyItemsTillLeafWithHead( parallelRouteState, parallelSeedData, head, + layerAssets, prefetchEntry ) } diff --git a/packages/next/src/client/components/router-reducer/invalidate-cache-below-flight-segmentpath.test.tsx b/packages/next/src/client/components/router-reducer/invalidate-cache-below-flight-segmentpath.test.tsx index afc5bce7b1af71..b9fc3adc7ce942 100644 --- a/packages/next/src/client/components/router-reducer/invalidate-cache-below-flight-segmentpath.test.tsx +++ b/packages/next/src/client/components/router-reducer/invalidate-cache-below-flight-segmentpath.test.tsx @@ -32,6 +32,8 @@ describe('invalidateCacheBelowFlightSegmentPath', () => { rsc: null, prefetchRsc: null, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, loading: null, parallelRoutes: new Map(), @@ -42,6 +44,8 @@ describe('invalidateCacheBelowFlightSegmentPath', () => { rsc: <>Root layout, prefetchRsc: null, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, lazyDataResolved: false, loading: null, @@ -56,6 +60,8 @@ describe('invalidateCacheBelowFlightSegmentPath', () => { rsc: <>Linking, prefetchRsc: null, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, lazyDataResolved: false, loading: null, @@ -70,6 +76,8 @@ describe('invalidateCacheBelowFlightSegmentPath', () => { rsc: <>Page, prefetchRsc: null, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, lazyDataResolved: false, loading: null, @@ -113,6 +121,8 @@ describe('invalidateCacheBelowFlightSegmentPath', () => { lazyData: null, lazyDataResolved: false, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, loading: null, parallelRoutes: new Map([ @@ -125,6 +135,8 @@ describe('invalidateCacheBelowFlightSegmentPath', () => { lazyData: null, lazyDataResolved: false, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, loading: null, parallelRoutes: new Map([ @@ -140,6 +152,8 @@ describe('invalidateCacheBelowFlightSegmentPath', () => { rsc: Page, prefetchRsc: null, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, lazyDataResolved: false, }, diff --git a/packages/next/src/client/components/router-reducer/invalidate-cache-below-flight-segmentpath.ts b/packages/next/src/client/components/router-reducer/invalidate-cache-below-flight-segmentpath.ts index 6688c829b07404..f252c1f590275e 100644 --- a/packages/next/src/client/components/router-reducer/invalidate-cache-below-flight-segmentpath.ts +++ b/packages/next/src/client/components/router-reducer/invalidate-cache-below-flight-segmentpath.ts @@ -51,6 +51,7 @@ export function invalidateCacheBelowFlightSegmentPath( rsc: childCacheNode.rsc, prefetchRsc: childCacheNode.prefetchRsc, head: childCacheNode.head, + layerAssets: childCacheNode.layerAssets, prefetchHead: childCacheNode.prefetchHead, parallelRoutes: new Map(childCacheNode.parallelRoutes), lazyDataResolved: childCacheNode.lazyDataResolved, diff --git a/packages/next/src/client/components/router-reducer/invalidate-cache-by-router-state.test.tsx b/packages/next/src/client/components/router-reducer/invalidate-cache-by-router-state.test.tsx index ab9804d0e62dfd..08ff5e003a659a 100644 --- a/packages/next/src/client/components/router-reducer/invalidate-cache-by-router-state.test.tsx +++ b/packages/next/src/client/components/router-reducer/invalidate-cache-by-router-state.test.tsx @@ -10,6 +10,8 @@ describe('invalidateCacheByRouterState', () => { rsc: null, prefetchRsc: null, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, loading: null, parallelRoutes: new Map(), @@ -20,6 +22,8 @@ describe('invalidateCacheByRouterState', () => { rsc: <>Root layout, prefetchRsc: null, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, lazyDataResolved: false, loading: null, @@ -34,6 +38,8 @@ describe('invalidateCacheByRouterState', () => { rsc: <>Linking, prefetchRsc: null, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, lazyDataResolved: false, loading: null, @@ -48,6 +54,8 @@ describe('invalidateCacheByRouterState', () => { rsc: <>Page, prefetchRsc: null, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, lazyDataResolved: false, loading: null, @@ -91,6 +99,8 @@ describe('invalidateCacheByRouterState', () => { rsc: null, prefetchRsc: null, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, lazyDataResolved: false, loading: null, diff --git a/packages/next/src/client/components/router-reducer/ppr-navigations.ts b/packages/next/src/client/components/router-reducer/ppr-navigations.ts index 35f975d0df5875..3aeae075dc4b69 100644 --- a/packages/next/src/client/components/router-reducer/ppr-navigations.ts +++ b/packages/next/src/client/components/router-reducer/ppr-navigations.ts @@ -67,7 +67,8 @@ export function updateCacheNodeOnNavigation( oldRouterState: FlightRouterState, newRouterState: FlightRouterState, prefetchData: CacheNodeSeedData, - prefetchHead: React.ReactNode + prefetchHead: React.ReactNode, + prefetchLayerAssets: React.ReactNode ): Task | null { // Diff the old and new trees to reuse the shared layouts. const oldRouterStateChildren = oldRouterState[1] @@ -125,7 +126,8 @@ export function updateCacheNodeOnNavigation( taskChild = spawnPendingTask( newRouterStateChild, prefetchDataChild !== undefined ? prefetchDataChild : null, - prefetchHead + prefetchHead, + prefetchLayerAssets ) } else if (newSegmentChild === DEFAULT_SEGMENT_KEY) { // This is another kind of leaf segment — a default route. @@ -145,7 +147,8 @@ export function updateCacheNodeOnNavigation( taskChild = spawnPendingTask( newRouterStateChild, prefetchDataChild !== undefined ? prefetchDataChild : null, - prefetchHead + prefetchHead, + prefetchLayerAssets ) } } else if ( @@ -164,7 +167,8 @@ export function updateCacheNodeOnNavigation( oldRouterStateChild, newRouterStateChild, prefetchDataChild, - prefetchHead + prefetchHead, + prefetchLayerAssets ) } else { // The server didn't send any prefetch data for this segment. This @@ -181,7 +185,8 @@ export function updateCacheNodeOnNavigation( taskChild = spawnPendingTask( newRouterStateChild, prefetchDataChild !== undefined ? prefetchDataChild : null, - prefetchHead + prefetchHead, + prefetchLayerAssets ) } } else { @@ -189,7 +194,8 @@ export function updateCacheNodeOnNavigation( taskChild = spawnPendingTask( newRouterStateChild, prefetchDataChild !== undefined ? prefetchDataChild : null, - prefetchHead + prefetchHead, + prefetchLayerAssets ) } @@ -233,6 +239,8 @@ export function updateCacheNodeOnNavigation( prefetchRsc: oldCacheNode.prefetchRsc, head: oldCacheNode.head, prefetchHead: oldCacheNode.prefetchHead, + layerAssets: oldCacheNode.layerAssets, + prefetchLayerAssets: oldCacheNode.prefetchLayerAssets, loading: oldCacheNode.loading, // Everything is cloned except for the children, which we computed above. @@ -274,13 +282,15 @@ function patchRouterStateWithNewChildren( function spawnPendingTask( routerState: FlightRouterState, prefetchData: CacheNodeSeedData | null, - prefetchHead: React.ReactNode + prefetchHead: React.ReactNode, + prefetchLayerAssets: React.ReactNode ): Task { // Create a task that will later be fulfilled by data from the server. const pendingCacheNode = createPendingCacheNode( routerState, prefetchData, - prefetchHead + prefetchHead, + prefetchLayerAssets ) return { route: routerState, @@ -303,7 +313,7 @@ function spawnTaskForMissingData(routerState: FlightRouterState): Task { // Create a task for a new subtree that wasn't prefetched by the server. // This shouldn't really ever happen but it's here just in case the Seed Data // Tree and the Router State Tree disagree unexpectedly. - const pendingCacheNode = createPendingCacheNode(routerState, null, null) + const pendingCacheNode = createPendingCacheNode(routerState, null, null, null) return { route: routerState, node: pendingCacheNode, @@ -334,10 +344,11 @@ export function listenForDynamicRequest( (response: FetchServerResponseResult) => { const flightData = response[0] for (const flightDataPath of flightData) { - const segmentPath = flightDataPath.slice(0, -3) - const serverRouterState = flightDataPath[flightDataPath.length - 3] - const dynamicData = flightDataPath[flightDataPath.length - 2] - const dynamicHead = flightDataPath[flightDataPath.length - 1] + const segmentPath = flightDataPath.slice(0, -4) + const serverRouterState = flightDataPath[flightDataPath.length - 4] + const dynamicData = flightDataPath[flightDataPath.length - 3] + const dynamicHead = flightDataPath[flightDataPath.length - 2] + const dynamicLayerAssets = flightDataPath[flightDataPath.length - 1] if (typeof segmentPath === 'string') { // Happens when navigating to page in `pages` from `app`. We shouldn't @@ -351,7 +362,8 @@ export function listenForDynamicRequest( segmentPath, serverRouterState, dynamicData, - dynamicHead + dynamicHead, + dynamicLayerAssets ) } @@ -372,7 +384,8 @@ function writeDynamicDataIntoPendingTask( segmentPath: FlightSegmentPath, serverRouterState: FlightRouterState, dynamicData: CacheNodeSeedData, - dynamicHead: React.ReactNode + dynamicHead: React.ReactNode, + dynamicLayerAssets: React.ReactNode ) { // The data sent by the server represents only a subtree of the app. We need // to find the part of the task tree that matches the server response, and @@ -411,7 +424,8 @@ function writeDynamicDataIntoPendingTask( task, serverRouterState, dynamicData, - dynamicHead + dynamicHead, + dynamicLayerAssets ) } @@ -419,7 +433,8 @@ function finishTaskUsingDynamicDataPayload( task: Task, serverRouterState: FlightRouterState, dynamicData: CacheNodeSeedData, - dynamicHead: React.ReactNode + dynamicHead: React.ReactNode, + dynamicLayerAssets: React.ReactNode ) { // dynamicData may represent a larger subtree than the task. Before we can // finish the task, we need to line them up. @@ -435,7 +450,8 @@ function finishTaskUsingDynamicDataPayload( task.route, serverRouterState, dynamicData, - dynamicHead + dynamicHead, + dynamicLayerAssets ) // Null this out to indicate that the task is complete. task.node = null @@ -466,7 +482,8 @@ function finishTaskUsingDynamicDataPayload( taskChild, serverRouterStateChild, dynamicDataChild, - dynamicHead + dynamicHead, + dynamicLayerAssets ) } } @@ -480,7 +497,8 @@ function finishTaskUsingDynamicDataPayload( function createPendingCacheNode( routerState: FlightRouterState, prefetchData: CacheNodeSeedData | null, - prefetchHead: React.ReactNode + prefetchHead: React.ReactNode, + prefetchLayerAssets: React.ReactNode ): ReadyCacheNode { const routerStateChildren = routerState[1] const prefetchDataChildren = prefetchData !== null ? prefetchData[1] : null @@ -500,7 +518,8 @@ function createPendingCacheNode( const newCacheNodeChild = createPendingCacheNode( routerStateChild, prefetchDataChild === undefined ? null : prefetchDataChild, - prefetchHead + prefetchHead, + prefetchLayerAssets ) const newSegmentMapChild: ChildSegmentMap = new Map() @@ -520,12 +539,14 @@ function createPendingCacheNode( prefetchRsc: maybePrefetchRsc !== undefined ? maybePrefetchRsc : null, prefetchHead: isLeafSegment ? prefetchHead : null, + prefetchLayerAssets, loading: maybePrefetchLoading !== undefined ? maybePrefetchLoading : null, // Create a deferred promise. This will be fulfilled once the dynamic // response is received from the server. - rsc: createDeferredRsc(), - head: isLeafSegment ? createDeferredRsc() : null, + rsc: createDeferredRsc() as React.ReactNode, + head: isLeafSegment ? (createDeferredRsc() as React.ReactNode) : null, + layerAssets: createDeferredRsc() as React.ReactNode, lazyDataResolved: false, } } @@ -535,7 +556,8 @@ function finishPendingCacheNode( taskState: FlightRouterState, serverState: FlightRouterState, dynamicData: CacheNodeSeedData, - dynamicHead: React.ReactNode + dynamicHead: React.ReactNode, + dynamicLayerAssets: React.ReactNode ): void { // Writes a dynamic response into an existing Cache Node tree. This does _not_ // create a new tree, it updates the existing tree in-place. So it must follow @@ -584,7 +606,8 @@ function finishPendingCacheNode( taskStateChild, serverStateChild, dataChild, - dynamicHead + dynamicHead, + dynamicLayerAssets ) } else { // The server never returned data for this segment. Trigger a lazy @@ -624,6 +647,12 @@ function finishPendingCacheNode( // been populated by a different navigation. We must not overwrite it. } + // Use the dynamic data from the server to fulfill the deferred layerAssets. + const layerAssets = cacheNode.layerAssets + if (isDeferredRsc(layerAssets)) { + layerAssets.resolve(dynamicLayerAssets) + } + // Check if this is a leaf segment. If so, it will have a `head` property with // a pending promise that needs to be resolved with the dynamic head from // the server. @@ -699,6 +728,13 @@ function abortPendingCacheNode( } } + // If layerAssets is pending, resolve it with an error. Since the RSC is part + // part of the same response, the error will already be handled above. + const layerAssets = cacheNode.layerAssets + if (isDeferredRsc(layerAssets)) { + layerAssets.resolve(null) + } + // Check if this is a leaf segment. If so, it will have a `head` property with // a pending promise that needs to be resolved. If an error was provided, we // will not resolve it with an error, since this is rendered at the root of @@ -761,7 +797,11 @@ export function updateCacheNodeOnPopstateRestoration( lazyData: null, rsc, head: oldCacheNode.head, + layerAssets: oldCacheNode.layerAssets, + prefetchLayerAssets: shouldUsePrefetch + ? oldCacheNode.prefetchLayerAssets + : null, prefetchHead: shouldUsePrefetch ? oldCacheNode.prefetchHead : null, prefetchRsc: shouldUsePrefetch ? oldCacheNode.prefetchRsc : null, loading: shouldUsePrefetch ? oldCacheNode.loading : null, diff --git a/packages/next/src/client/components/router-reducer/reducers/fast-refresh-reducer.ts b/packages/next/src/client/components/router-reducer/reducers/fast-refresh-reducer.ts index b83414a2bb9ef5..271204e637c4c6 100644 --- a/packages/next/src/client/components/router-reducer/reducers/fast-refresh-reducer.ts +++ b/packages/next/src/client/components/router-reducer/reducers/fast-refresh-reducer.ts @@ -61,7 +61,7 @@ function fastRefreshReducerImpl( for (const flightDataPath of flightData) { // FlightDataPath with more than two items means unexpected Flight data was returned - if (flightDataPath.length !== 3) { + if (flightDataPath.length !== 4) { // TODO-APP: handle this case better console.log('REFRESH FAILED') return state diff --git a/packages/next/src/client/components/router-reducer/reducers/find-head-in-cache.test.tsx b/packages/next/src/client/components/router-reducer/reducers/find-head-in-cache.test.tsx index ce4f5f6ac0fc5f..39f36e1ce67385 100644 --- a/packages/next/src/client/components/router-reducer/reducers/find-head-in-cache.test.tsx +++ b/packages/next/src/client/components/router-reducer/reducers/find-head-in-cache.test.tsx @@ -30,6 +30,8 @@ describe('findHeadInCache', () => { rsc: null, prefetchRsc: null, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, lazyDataResolved: false, loading: null, @@ -44,6 +46,8 @@ describe('findHeadInCache', () => { rsc: null, prefetchRsc: null, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, lazyDataResolved: false, loading: null, @@ -57,6 +61,8 @@ describe('findHeadInCache', () => { lazyData: null, lazyDataResolved: false, head: null, + layerAssets: null, + prefetchLayerAssets: null, prefetchHead: null, loading: null, parallelRoutes: new Map([ @@ -78,6 +84,8 @@ describe('findHeadInCache', () => { About page! ), + layerAssets: null, + prefetchLayerAssets: null, }, ], ]), diff --git a/packages/next/src/client/components/router-reducer/reducers/navigate-reducer.ts b/packages/next/src/client/components/router-reducer/reducers/navigate-reducer.ts index 04f9a6b86c57f0..4ca7257d2e8210 100644 --- a/packages/next/src/client/components/router-reducer/reducers/navigate-reducer.ts +++ b/packages/next/src/client/components/router-reducer/reducers/navigate-reducer.ts @@ -172,10 +172,10 @@ function navigateReducer_noPPR( for (const flightDataPath of flightData) { const flightSegmentPath = flightDataPath.slice( 0, - -4 + -5 ) as unknown as FlightSegmentPath // The one before last item is the router state tree patch - const treePatch = flightDataPath.slice(-3)[0] as FlightRouterState + const treePatch = flightDataPath.slice(-4)[0] as FlightRouterState // TODO-APP: remove '' const flightSegmentPathWithLeadingEmpty = ['', ...flightSegmentPath] @@ -364,10 +364,10 @@ function navigateReducer_PPR( for (const flightDataPath of flightData) { const flightSegmentPath = flightDataPath.slice( 0, - -4 + -5 ) as unknown as FlightSegmentPath // The one before last item is the router state tree patch - const treePatch = flightDataPath.slice(-3)[0] as FlightRouterState + const treePatch = flightDataPath.slice(-4)[0] as FlightRouterState // TODO-APP: remove '' const flightSegmentPathWithLeadingEmpty = ['', ...flightSegmentPath] @@ -406,18 +406,20 @@ function navigateReducer_PPR( // TODO: We should get rid of the else branch and do all navigations // via updateCacheNodeOnNavigation. The current structure is just // an incremental step. - flightDataPath.length === 3 + flightDataPath.length === 4 ) { const prefetchedTree: FlightRouterState = flightDataPath[0] const seedData = flightDataPath[1] const head = flightDataPath[2] + const layerAssets = flightDataPath[3] const task = updateCacheNodeOnNavigation( currentCache, currentTree, prefetchedTree, seedData, - head + head, + layerAssets ) if (task !== null && task.node !== null) { // We've created a new Cache Node tree that contains a prefetched diff --git a/packages/next/src/client/components/router-reducer/reducers/refresh-reducer.ts b/packages/next/src/client/components/router-reducer/reducers/refresh-reducer.ts index f2147b60d56c85..ba708f2cce3b15 100644 --- a/packages/next/src/client/components/router-reducer/reducers/refresh-reducer.ts +++ b/packages/next/src/client/components/router-reducer/reducers/refresh-reducer.ts @@ -61,7 +61,7 @@ export function refreshReducer( for (const flightDataPath of flightData) { // FlightDataPath with more than two items means unexpected Flight data was returned - if (flightDataPath.length !== 3) { + if (flightDataPath.length !== 4) { // TODO-APP: handle this case better console.log('REFRESH FAILED') return state @@ -99,7 +99,7 @@ export function refreshReducer( } // The one before last item is the router state tree patch - const [cacheNodeSeedData, head] = flightDataPath.slice(-2) + const [cacheNodeSeedData, head, layerAssets] = flightDataPath.slice(-3) // Handles case where prefetch only returns the router tree patch without rendered components. if (cacheNodeSeedData !== null) { @@ -112,7 +112,8 @@ export function refreshReducer( undefined, treePatch, cacheNodeSeedData, - head + head, + layerAssets ) mutable.prefetchCache = new Map() } diff --git a/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts b/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts index 65bd7446769d13..aa23e98189a252 100644 --- a/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts +++ b/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts @@ -217,7 +217,7 @@ export function serverActionReducer( for (const flightDataPath of flightData) { // FlightDataPath with more than two items means unexpected Flight data was returned - if (flightDataPath.length !== 3) { + if (flightDataPath.length !== 4) { // TODO-APP: handle this case better console.log('SERVER ACTION APPLY FAILED') return state @@ -249,7 +249,7 @@ export function serverActionReducer( } // The one before last item is the router state tree patch - const [cacheNodeSeedData, head] = flightDataPath.slice(-2) + const [cacheNodeSeedData, head, layerAssets] = flightDataPath.slice(-3) const rsc = cacheNodeSeedData !== null ? cacheNodeSeedData[2] : null // Handles case where prefetch only returns the router tree patch without rendered components. @@ -263,7 +263,8 @@ export function serverActionReducer( undefined, treePatch, cacheNodeSeedData, - head + head, + layerAssets ) await refreshInactiveParallelSegments({ diff --git a/packages/next/src/client/components/router-reducer/reducers/server-patch-reducer.ts b/packages/next/src/client/components/router-reducer/reducers/server-patch-reducer.ts index 5fe183d7eaea7f..c6434bbca547de 100644 --- a/packages/next/src/client/components/router-reducer/reducers/server-patch-reducer.ts +++ b/packages/next/src/client/components/router-reducer/reducers/server-patch-reducer.ts @@ -39,10 +39,10 @@ export function serverPatchReducer( let currentCache = state.cache for (const flightDataPath of flightData) { - // Slices off the last segment (which is at -4) as it doesn't exist in the tree yet - const flightSegmentPath = flightDataPath.slice(0, -4) + // Slices off the last segment (which is at -5) as it doesn't exist in the tree yet + const flightSegmentPath = flightDataPath.slice(0, -5) - const [treePatch] = flightDataPath.slice(-3, -2) + const [treePatch] = flightDataPath.slice(-4, -3) const newTree = applyRouterStatePatchToTree( // TODO-APP: remove '' ['', ...flightSegmentPath], diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx index 0c7c66133f009a..bd833c5aa7a4db 100644 --- a/packages/next/src/server/app-render/app-render.tsx +++ b/packages/next/src/server/app-render/app-render.tsx @@ -476,6 +476,7 @@ async function ReactServerApp({ tree, ctx, asNotFound }: ReactServerAppProps) { } + initialLayerAssets={null} globalErrorComponent={GlobalError} // This is used to provide debug information (when in development mode) // about which slots were not filled by page components while creating the component tree. @@ -555,6 +556,7 @@ async function ReactServerError({ initialCanonicalUrl={urlPathname} initialTree={initialTree} initialHead={head} + initialLayerAssets={null} globalErrorComponent={GlobalError} initialSeedData={initialSeedData} missingSlots={new Set()} diff --git a/packages/next/src/server/app-render/types.ts b/packages/next/src/server/app-render/types.ts index 7ed9c21b9e6d85..fa26d055c1526b 100644 --- a/packages/next/src/server/app-render/types.ts +++ b/packages/next/src/server/app-render/types.ts @@ -101,7 +101,8 @@ export type FlightDataPath = /* segment of the rendered slice: */ Segment, /* treePatch */ FlightRouterState, /* cacheNodeSeedData */ CacheNodeSeedData, // Can be null during prefetch if there's no loading component - /* head */ React.ReactNode | null + /* head */ React.ReactNode | null, + /* layerAssets (imported styles/scripts) */ React.ReactNode | null ] /** diff --git a/packages/next/src/server/app-render/walk-tree-with-flight-router-state.tsx b/packages/next/src/server/app-render/walk-tree-with-flight-router-state.tsx index 3a0dd77ea61ec8..540dfdcf35ac33 100644 --- a/packages/next/src/server/app-render/walk-tree-with-flight-router-state.tsx +++ b/packages/next/src/server/app-render/walk-tree-with-flight-router-state.tsx @@ -4,7 +4,7 @@ import type { FlightSegmentPath, Segment, } from './types' -import React from 'react' +import type React from 'react' import { canSegmentBeOverridden, matchSegment, @@ -136,7 +136,7 @@ export async function walkTreeWithFlightRouterState({ if (shouldSkipComponentTree) { // Send only the router state - return [[overriddenSegment, routerState, null, null]] + return [[overriddenSegment, routerState, null, null, null]] } else { // Create component tree using the slice of the loaderTree const { seedData } = await createComponentTree( @@ -166,14 +166,10 @@ export async function walkTreeWithFlightRouterState({ injectedJS: new Set(injectedJS), injectedFontPreloadTags: new Set(injectedFontPreloadTags), }) - const head = ( - <> - {layerAssets} - {rscPayloadHead} - - ) - return [[overriddenSegment, routerState, seedData, head]] + return [ + [overriddenSegment, routerState, seedData, rscPayloadHead, layerAssets], + ] } } diff --git a/packages/next/src/shared/lib/app-router-context.shared-runtime.ts b/packages/next/src/shared/lib/app-router-context.shared-runtime.ts index 3bb5b14b7ef838..f60fbff323ea9a 100644 --- a/packages/next/src/shared/lib/app-router-context.shared-runtime.ts +++ b/packages/next/src/shared/lib/app-router-context.shared-runtime.ts @@ -57,6 +57,8 @@ export type LazyCacheNode = { prefetchHead: React.ReactNode head: React.ReactNode + prefetchLayerAssets: React.ReactNode + layerAssets: React.ReactNode loading: LoadingModuleData @@ -104,6 +106,8 @@ export type ReadyCacheNode = { lazyData: null prefetchHead: React.ReactNode head: React.ReactNode + prefetchLayerAssets: React.ReactNode + layerAssets: React.ReactNode loading: LoadingModuleData