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
100 changes: 93 additions & 7 deletions packages/next/src/lib/metadata/metadata.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ import type { WorkStore } from '../../server/app-render/work-async-storage.exter
import { createServerSearchParamsForMetadata } from '../../server/request/search-params'
import { createServerPathnameForMetadata } from '../../server/request/pathname'
import { isPostpone } from '../../server/lib/router-utils/is-postpone'
import { workUnitAsyncStorage } from '../../server/app-render/work-unit-async-storage.external'
import {
RenderStage,
type StagedRenderingController,
} from '../../server/app-render/staged-rendering'

import {
MetadataBoundary,
Expand All @@ -58,6 +63,7 @@ export function createMetadataComponents({
errorType,
workStore,
serveStreamingMetadata,
isRuntimePrefetchable,
}: {
tree: LoaderTree
pathname: string
Expand All @@ -67,26 +73,59 @@ export function createMetadataComponents({
errorType?: MetadataErrorType | 'redirect'
workStore: WorkStore
serveStreamingMetadata: boolean
isRuntimePrefetchable: boolean
}): {
Viewport: React.ComponentType
Metadata: React.ComponentType
MetadataOutlet: React.ComponentType
} {
const searchParams = createServerSearchParamsForMetadata(
parsedQuery,
workStore
workStore,
isRuntimePrefetchable
)
const pathnameForMetadata = createServerPathnameForMetadata(
pathname,
workStore
)

async function Viewport() {
// Gate metadata to the correct render stage. If the page is not
// runtime-prefetchable, defer until the Static stage so that
// prefetchable segments get a head start.
if (!isRuntimePrefetchable) {
const workUnitStore = workUnitAsyncStorage.getStore()
if (workUnitStore) {
let stagedRendering: StagedRenderingController | null | undefined
switch (workUnitStore.type) {
case 'request':
case 'prerender-runtime':
stagedRendering = workUnitStore.stagedRendering
break
case 'prerender':
case 'prerender-client':
case 'validation-client':
case 'prerender-ppr':
case 'prerender-legacy':
case 'cache':
case 'private-cache':
case 'unstable-cache':
break
default:
workUnitStore satisfies never
}
if (stagedRendering) {
await stagedRendering.waitForStage(RenderStage.Static)
}
}
}

const tags = await getResolvedViewport(
tree,
searchParams,
getDynamicParamFromSegment,
workStore,
isRuntimePrefetchable,
errorType
).catch((viewportErr) => {
// When Legacy PPR is enabled viewport can reject with a Postpone type
Expand All @@ -100,7 +139,8 @@ export function createMetadataComponents({
tree,
searchParams,
getDynamicParamFromSegment,
workStore
workStore,
isRuntimePrefetchable
).catch(() => null)
}
// We're going to throw the error from the metadata outlet so we just render null here instead
Expand All @@ -120,13 +160,44 @@ export function createMetadataComponents({
}

async function Metadata() {
// Gate metadata to the correct render stage. If the page is not
// runtime-prefetchable, defer until the Static stage so that
// prefetchable segments get a head start.
if (!isRuntimePrefetchable) {
const workUnitStore = workUnitAsyncStorage.getStore()
if (workUnitStore) {
let stagedRendering: StagedRenderingController | null | undefined
switch (workUnitStore.type) {
case 'request':
case 'prerender-runtime':
stagedRendering = workUnitStore.stagedRendering
break
case 'prerender':
case 'prerender-client':
case 'validation-client':
case 'prerender-ppr':
case 'prerender-legacy':
case 'cache':
case 'private-cache':
case 'unstable-cache':
break
default:
workUnitStore satisfies never
}
if (stagedRendering) {
await stagedRendering.waitForStage(RenderStage.Static)
}
}
}

const tags = await getResolvedMetadata(
tree,
pathnameForMetadata,
searchParams,
getDynamicParamFromSegment,
metadataContext,
workStore,
isRuntimePrefetchable,
errorType
).catch((metadataErr) => {
// When Legacy PPR is enabled metadata can reject with a Postpone type
Expand All @@ -142,7 +213,8 @@ export function createMetadataComponents({
searchParams,
getDynamicParamFromSegment,
metadataContext,
workStore
workStore,
isRuntimePrefetchable
).catch(() => null)
}
// We're going to throw the error from the metadata outlet so we just render null here instead
Expand Down Expand Up @@ -184,13 +256,15 @@ export function createMetadataComponents({
getDynamicParamFromSegment,
metadataContext,
workStore,
isRuntimePrefetchable,
errorType
),
getResolvedViewport(
tree,
searchParams,
getDynamicParamFromSegment,
workStore,
isRuntimePrefetchable,
errorType
),
]).then(() => null)
Expand Down Expand Up @@ -224,6 +298,7 @@ async function getResolvedMetadataImpl(
getDynamicParamFromSegment: GetDynamicParamFromSegment,
metadataContext: MetadataContext,
workStore: WorkStore,
isRuntimePrefetchable: boolean,
errorType?: MetadataErrorType | 'redirect'
): Promise<React.ReactNode> {
const errorConvention = errorType === 'redirect' ? undefined : errorType
Expand All @@ -234,6 +309,7 @@ async function getResolvedMetadataImpl(
getDynamicParamFromSegment,
metadataContext,
workStore,
isRuntimePrefetchable,
errorConvention
)
}
Expand All @@ -245,7 +321,8 @@ async function getNotFoundMetadataImpl(
searchParams: Promise<ParsedUrlQuery>,
getDynamicParamFromSegment: GetDynamicParamFromSegment,
metadataContext: MetadataContext,
workStore: WorkStore
workStore: WorkStore,
isRuntimePrefetchable: boolean
): Promise<React.ReactNode> {
const notFoundErrorConvention = 'not-found'
return renderMetadata(
Expand All @@ -255,6 +332,7 @@ async function getNotFoundMetadataImpl(
getDynamicParamFromSegment,
metadataContext,
workStore,
isRuntimePrefetchable,
notFoundErrorConvention
)
}
Expand All @@ -265,6 +343,7 @@ async function getResolvedViewportImpl(
searchParams: Promise<ParsedUrlQuery>,
getDynamicParamFromSegment: GetDynamicParamFromSegment,
workStore: WorkStore,
isRuntimePrefetchable: boolean,
errorType?: MetadataErrorType | 'redirect'
): Promise<React.ReactNode> {
const errorConvention = errorType === 'redirect' ? undefined : errorType
Expand All @@ -273,6 +352,7 @@ async function getResolvedViewportImpl(
searchParams,
getDynamicParamFromSegment,
workStore,
isRuntimePrefetchable,
errorConvention
)
}
Expand All @@ -282,14 +362,16 @@ async function getNotFoundViewportImpl(
tree: LoaderTree,
searchParams: Promise<ParsedUrlQuery>,
getDynamicParamFromSegment: GetDynamicParamFromSegment,
workStore: WorkStore
workStore: WorkStore,
isRuntimePrefetchable: boolean
): Promise<React.ReactNode> {
const notFoundErrorConvention = 'not-found'
return renderViewport(
tree,
searchParams,
getDynamicParamFromSegment,
workStore,
isRuntimePrefetchable,
notFoundErrorConvention
)
}
Expand All @@ -301,6 +383,7 @@ async function renderMetadata(
getDynamicParamFromSegment: GetDynamicParamFromSegment,
metadataContext: MetadataContext,
workStore: WorkStore,
isRuntimePrefetchable: boolean,
errorConvention?: MetadataErrorType
) {
const resolvedMetadata = await resolveMetadata(
Expand All @@ -310,7 +393,8 @@ async function renderMetadata(
errorConvention,
getDynamicParamFromSegment,
workStore,
metadataContext
metadataContext,
isRuntimePrefetchable
)
const elements: Array<React.ReactNode> =
createMetadataElements(resolvedMetadata)
Expand All @@ -328,14 +412,16 @@ async function renderViewport(
searchParams: Promise<ParsedUrlQuery>,
getDynamicParamFromSegment: GetDynamicParamFromSegment,
workStore: WorkStore,
isRuntimePrefetchable: boolean,
errorConvention?: MetadataErrorType
) {
const resolvedViewport = await resolveViewport(
tree,
searchParams,
errorConvention,
getDynamicParamFromSegment,
workStore
workStore,
isRuntimePrefetchable
)

const elements: Array<React.ReactNode> =
Expand Down
Loading
Loading