diff --git a/packages/next/src/client/components/router-reducer/reducers/find-head-in-cache.ts b/packages/next/src/client/components/router-reducer/reducers/find-head-in-cache.ts
index db6f9eb66502d..471b1804e7ac3 100644
--- a/packages/next/src/client/components/router-reducer/reducers/find-head-in-cache.ts
+++ b/packages/next/src/client/components/router-reducer/reducers/find-head-in-cache.ts
@@ -1,5 +1,6 @@
import type { FlightRouterState } from '../../../../server/app-render/types'
import type { CacheNode } from '../../../../shared/lib/app-router-context.shared-runtime'
+import { DEFAULT_SEGMENT_KEY } from '../../../../shared/lib/segment'
import { createRouterCacheKey } from '../create-router-cache-key'
export function findHeadInCache(
@@ -22,28 +23,22 @@ function findHeadInCacheImpl(
// First try the 'children' parallel route if it exists
// when starting from the "root", this corresponds with the main page component
- if (parallelRoutes.children) {
- const [segment, childParallelRoutes] = parallelRoutes.children
- const childSegmentMap = cache.parallelRoutes.get('children')
- if (childSegmentMap) {
- const cacheKey = createRouterCacheKey(segment)
- const cacheNode = childSegmentMap.get(cacheKey)
- if (cacheNode) {
- const item = findHeadInCacheImpl(
- cacheNode,
- childParallelRoutes,
- keyPrefix + '/' + cacheKey
- )
- if (item) return item
- }
- }
- }
+ const parallelRoutesKeys = Object.keys(parallelRoutes).filter(
+ (key) => key !== 'children'
+ )
- // if we didn't find metadata in the page slot, check the other parallel routes
- for (const key in parallelRoutes) {
- if (key === 'children') continue // already checked above
+ // if we are at the root, we need to check the children slot first
+ if ('children' in parallelRoutes) {
+ parallelRoutesKeys.unshift('children')
+ }
+ for (const key of parallelRoutesKeys) {
const [segment, childParallelRoutes] = parallelRoutes[key]
+ // If the parallel is not matched and using the default segment,
+ // skip searching the head from it.
+ if (segment === DEFAULT_SEGMENT_KEY) {
+ continue
+ }
const childSegmentMap = cache.parallelRoutes.get(key)
if (!childSegmentMap) {
continue
diff --git a/packages/next/src/lib/metadata/metadata.tsx b/packages/next/src/lib/metadata/metadata.tsx
index 9b8c593abec53..1413bc4f11078 100644
--- a/packages/next/src/lib/metadata/metadata.tsx
+++ b/packages/next/src/lib/metadata/metadata.tsx
@@ -206,9 +206,11 @@ export function createMetadataComponents({
const promise = resolveFinalMetadata()
if (serveStreamingMetadata) {
return (
-
-
-
+
+
+
+
+
)
}
const metadataState = await promise
diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx
index 5e9d8da2e616b..9e334353965ed 100644
--- a/packages/next/src/server/app-render/app-render.tsx
+++ b/packages/next/src/server/app-render/app-render.tsx
@@ -245,6 +245,8 @@ interface ParseRequestHeadersOptions {
}
const flightDataPathHeadKey = 'h'
+const getFlightViewportKey = (requestId: string) => requestId + 'v'
+const getFlightMetadataKey = (requestId: string) => requestId + 'm'
interface ParsedRequestHeaders {
/**
@@ -336,30 +338,6 @@ function createNotFoundLoaderTree(loaderTree: LoaderTree): LoaderTree {
]
}
-function createDivergedMetadataComponents(
- Metadata: React.ComponentType,
- serveStreamingMetadata: boolean
-): {
- StaticMetadata: React.ComponentType<{}>
- StreamingMetadata: React.ComponentType<{}> | null
-} {
- function EmptyMetadata() {
- return null
- }
- const StreamingMetadata: React.ComponentType | null = serveStreamingMetadata
- ? Metadata
- : null
-
- const StaticMetadata: React.ComponentType<{}> = serveStreamingMetadata
- ? EmptyMetadata
- : Metadata
-
- return {
- StaticMetadata,
- StreamingMetadata,
- }
-}
-
/**
* Returns a function that parses the dynamic segment and return the associated value.
*/
@@ -527,14 +505,6 @@ async function generateDynamicRSCPayload(
serveStreamingMetadata,
})
- const { StreamingMetadata, StaticMetadata } =
- createDivergedMetadataComponents(() => {
- return (
- // Adding requestId as react key to make metadata remount for each render
-
- )
- }, serveStreamingMetadata)
-
flightData = (
await walkTreeWithFlightRouterState({
ctx,
@@ -551,9 +521,9 @@ async function generateDynamicRSCPayload(
isPossibleServerAction={ctx.isPossibleServerAction}
/>
{/* Adding requestId as react key to make metadata remount for each render */}
-
- {StreamingMetadata ? : null}
-
+
+ {/* Not add requestId as react key to ensure segment prefetch could result consistently if nothing changed */}
+
),
injectedCSS: new Set(),
@@ -857,14 +827,6 @@ async function getRSCPayload(
const preloadCallbacks: PreloadCallbacks = []
- const { StreamingMetadata, StaticMetadata } =
- createDivergedMetadataComponents(() => {
- return (
- // Not add requestId as react key to ensure segment prefetch could result consistently if nothing changed
-
- )
- }, serveStreamingMetadata)
-
const seedData = await createComponentTree({
ctx,
loaderTree: tree,
@@ -878,7 +840,6 @@ async function getRSCPayload(
missingSlots,
preloadCallbacks,
authInterrupts: ctx.renderOpts.experimental.authInterrupts,
- StreamingMetadata,
StreamingMetadataOutlet,
})
@@ -896,8 +857,9 @@ async function getRSCPayload(
statusCode={ctx.res.statusCode}
isPossibleServerAction={ctx.isPossibleServerAction}
/>
-
-
+
+ {/* Not add requestId as react key to ensure segment prefetch could result consistently if nothing changed */}
+
)
@@ -984,16 +946,8 @@ async function getErrorRSCPayload(
serveStreamingMetadata: serveStreamingMetadata,
})
- const { StreamingMetadata, StaticMetadata } =
- createDivergedMetadataComponents(
- () => (
-
- {/* Adding requestId as react key to make metadata remount for each render */}
-
-
- ),
- serveStreamingMetadata
- )
+ // {/* Adding requestId as react key to make metadata remount for each render */}
+ const metadata =
const initialHead = (
@@ -1003,12 +957,11 @@ async function getErrorRSCPayload(
isPossibleServerAction={ctx.isPossibleServerAction}
/>
{/* Adding requestId as react key to make metadata remount for each render */}
-
+
{process.env.NODE_ENV === 'development' && (
)}
- {StreamingMetadata ? : null}
-
+ {metadata}
)
@@ -1028,10 +981,7 @@ async function getErrorRSCPayload(
const seedData: CacheNodeSeedData = [
initialTree[0],
-
- {StreamingMetadata ? : null}
-
-
+ {metadata}
{process.env.NODE_ENV !== 'production' && err ? (
preloadCallbacks: PreloadCallbacks
authInterrupts: boolean
- StreamingMetadata: React.ComponentType | null
StreamingMetadataOutlet: React.ComponentType
}): Promise {
return getTracer().trace(
@@ -76,7 +75,6 @@ async function createComponentTreeInternal({
missingSlots,
preloadCallbacks,
authInterrupts,
- StreamingMetadata,
StreamingMetadataOutlet,
}: {
loaderTree: LoaderTree
@@ -91,7 +89,6 @@ async function createComponentTreeInternal({
missingSlots?: Set
preloadCallbacks: PreloadCallbacks
authInterrupts: boolean
- StreamingMetadata: React.ComponentType | null
StreamingMetadataOutlet: React.ComponentType | null
}): Promise {
const {
@@ -390,7 +387,6 @@ async function createComponentTreeInternal({
// Resolve the segment param
const actualSegment = segmentParam ? segmentParam.treeSegment : segment
- const metadata = StreamingMetadata ? : undefined
// Use the same condition to render metadataOutlet as metadata
const metadataOutlet = StreamingMetadataOutlet ? (
@@ -513,7 +509,6 @@ async function createComponentTreeInternal({
missingSlots,
preloadCallbacks,
authInterrupts,
- StreamingMetadata: isChildrenRouteKey ? StreamingMetadata : null,
// `StreamingMetadataOutlet` is used to conditionally throw. In the case of parallel routes we will have more than one page
// but we only want to throw on the first one.
StreamingMetadataOutlet: isChildrenRouteKey
@@ -699,12 +694,6 @@ async function createComponentTreeInternal({
actualSegment,
{pageElement}
- {/*
- * The order here matters since a parent might call findDOMNode().
- * findDOMNode() will return the first child if multiple children are rendered.
- * But React will hoist metadata into which breaks scroll handling.
- */}
- {metadata}
{layerAssets}
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 5dffcc577c69b..d90beba53fcd5 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
@@ -202,7 +202,6 @@ export async function walkTreeWithFlightRouterState({
getMetadataReady,
preloadCallbacks,
authInterrupts: experimental.authInterrupts,
- StreamingMetadata: null,
StreamingMetadataOutlet,
}
)
diff --git a/test/e2e/app-dir/metadata-streaming-parallel-routes/app/layout.tsx b/test/e2e/app-dir/metadata-streaming-parallel-routes/app/layout.tsx
index 3d424723b93b1..1f882fc919b72 100644
--- a/test/e2e/app-dir/metadata-streaming-parallel-routes/app/layout.tsx
+++ b/test/e2e/app-dir/metadata-streaming-parallel-routes/app/layout.tsx
@@ -14,6 +14,11 @@ export default function Root({ children }: { children: ReactNode }) {
{`to /parallel-routes-default`}
+
+
+ {`to /parallel-routes-no-children`}
+
+
{children}
diff --git a/test/e2e/app-dir/metadata-streaming-parallel-routes/app/parallel-routes-no-children/@bar/default.tsx b/test/e2e/app-dir/metadata-streaming-parallel-routes/app/parallel-routes-no-children/@bar/default.tsx
new file mode 100644
index 0000000000000..e355998a4433c
--- /dev/null
+++ b/test/e2e/app-dir/metadata-streaming-parallel-routes/app/parallel-routes-no-children/@bar/default.tsx
@@ -0,0 +1,3 @@
+export default function Page() {
+ return 'default @bar'
+}
diff --git a/test/e2e/app-dir/metadata-streaming-parallel-routes/app/parallel-routes-no-children/@bar/first/page.tsx b/test/e2e/app-dir/metadata-streaming-parallel-routes/app/parallel-routes-no-children/@bar/first/page.tsx
new file mode 100644
index 0000000000000..85375256fc6db
--- /dev/null
+++ b/test/e2e/app-dir/metadata-streaming-parallel-routes/app/parallel-routes-no-children/@bar/first/page.tsx
@@ -0,0 +1,7 @@
+export default function Page() {
+ return