Skip to content

Commit 74e23a3

Browse files
committed
Resolve Viewport separately from Metdaata
Viewport blocks the root while Metadata no longer does. However Metadata requires the reading of dynamic params for things like icon loading and other features that require the path whereas viewport does not. Reading the path in fallback prerenders is considered dynamic when DIO is enabled which means that any dynamic route with a file based metadata such as an icon will trigger dynamic at the root. To Address this we simply resolve viewport separately from metadata.
1 parent e9a3c72 commit 74e23a3

File tree

3 files changed

+174
-28
lines changed

3 files changed

+174
-28
lines changed

packages/next/src/lib/metadata/metadata.tsx

+3-4
Original file line numberDiff line numberDiff line change
@@ -355,17 +355,16 @@ async function renderViewport(
355355
workStore: WorkStore,
356356
errorConvention?: MetadataErrorType
357357
) {
358-
const notFoundResolvedViewport = await resolveViewport(
358+
const resolvedViewport = await resolveViewport(
359359
tree,
360360
searchParams,
361361
errorConvention,
362362
getDynamicParamFromSegment,
363363
workStore
364364
)
365365

366-
const elements: Array<React.ReactNode> = createViewportElements(
367-
notFoundResolvedViewport
368-
)
366+
const elements: Array<React.ReactNode> =
367+
createViewportElements(resolvedViewport)
369368
return (
370369
<>
371370
{elements.map((el, index) => {

packages/next/src/lib/metadata/resolve-metadata.test.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ function accumulateMetadata(metadataItems: MetadataItems) {
1212
const fullMetadataItems: FullMetadataItems = metadataItems.map((item) => [
1313
item[0],
1414
item[1],
15-
null,
1615
])
1716
return originAccumulateMetadata(fullMetadataItems, {
1817
pathname: '/test',
@@ -23,9 +22,7 @@ function accumulateMetadata(metadataItems: MetadataItems) {
2322

2423
function accumulateViewport(viewportExports: Viewport[]) {
2524
// skip the first two arguments (metadata and static metadata)
26-
return originAccumulateViewport(
27-
viewportExports.map((item) => [null, null, item])
28-
)
25+
return originAccumulateViewport(viewportExports.map((item) => item))
2926
}
3027

3128
function mapUrlsToStrings(obj: any) {

packages/next/src/lib/metadata/resolve-metadata.ts

+170-20
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,11 @@ type ViewportResolver = (
6868

6969
export type MetadataErrorType = 'not-found' | 'forbidden' | 'unauthorized'
7070

71-
export type MetadataItems = [
72-
Metadata | MetadataResolver | null,
73-
StaticMetadata,
74-
Viewport | ViewportResolver | null,
75-
][]
71+
export type MetadataItems = Array<
72+
[Metadata | MetadataResolver | null, StaticMetadata]
73+
>
74+
75+
export type ViewportItems = Array<Viewport | ViewportResolver | null>
7676

7777
type TitleTemplates = {
7878
title: string | null
@@ -457,22 +457,65 @@ async function collectMetadata({
457457
const staticFilesMetadata = await resolveStaticMetadata(tree[2], props)
458458
const metadataExport = mod ? getDefinedMetadata(mod, props, { route }) : null
459459

460-
const viewportExport = mod ? getDefinedViewport(mod, props, { route }) : null
461-
462-
metadataItems.push([metadataExport, staticFilesMetadata, viewportExport])
460+
metadataItems.push([metadataExport, staticFilesMetadata])
463461

464462
if (hasErrorConventionComponent && errorConvention) {
465463
const errorMod = await getComponentTypeModule(tree, errorConvention)
466-
const errorViewportExport = errorMod
467-
? getDefinedViewport(errorMod, props, { route })
468-
: null
469464
const errorMetadataExport = errorMod
470465
? getDefinedMetadata(errorMod, props, { route })
471466
: null
472467

473468
errorMetadataItem[0] = errorMetadataExport
474469
errorMetadataItem[1] = staticFilesMetadata
475-
errorMetadataItem[2] = errorViewportExport
470+
}
471+
}
472+
473+
// [layout.metadata, static files metadata] -> ... -> [page.metadata, static files metadata]
474+
async function collectViewport({
475+
tree,
476+
viewportItems,
477+
errorViewportItemRef,
478+
props,
479+
route,
480+
errorConvention,
481+
}: {
482+
tree: LoaderTree
483+
viewportItems: ViewportItems
484+
errorViewportItemRef: ErrorViewportItemRef
485+
props: any
486+
route: string
487+
errorConvention?: MetadataErrorType
488+
}) {
489+
let mod
490+
let modType
491+
const hasErrorConventionComponent = Boolean(
492+
errorConvention && tree[2][errorConvention]
493+
)
494+
if (errorConvention) {
495+
mod = await getComponentTypeModule(tree, 'layout')
496+
modType = errorConvention
497+
} else {
498+
const { mod: layoutOrPageMod, modType: layoutOrPageModType } =
499+
await getLayoutOrPageModule(tree)
500+
mod = layoutOrPageMod
501+
modType = layoutOrPageModType
502+
}
503+
504+
if (modType) {
505+
route += `/${modType}`
506+
}
507+
508+
const viewportExport = mod ? getDefinedViewport(mod, props, { route }) : null
509+
510+
viewportItems.push(viewportExport)
511+
512+
if (hasErrorConventionComponent && errorConvention) {
513+
const errorMod = await getComponentTypeModule(tree, errorConvention)
514+
const errorViewportExport = errorMod
515+
? getDefinedViewport(errorMod, props, { route })
516+
: null
517+
518+
errorViewportItemRef.current = errorViewportExport
476519
}
477520
}
478521

@@ -485,7 +528,7 @@ const resolveMetadataItems = cache(async function (
485528
) {
486529
const parentParams = {}
487530
const metadataItems: MetadataItems = []
488-
const errorMetadataItem: MetadataItems[number] = [null, null, null]
531+
const errorMetadataItem: MetadataItems[number] = [null, null]
489532
const treePrefix = undefined
490533
return resolveMetadataItemsImpl(
491534
metadataItems,
@@ -580,6 +623,113 @@ async function resolveMetadataItemsImpl(
580623
return metadataItems
581624
}
582625

626+
type ErrorViewportItemRef = { current: ViewportItems[number] }
627+
const resolveViewportItems = cache(async function (
628+
tree: LoaderTree,
629+
searchParams: Promise<ParsedUrlQuery>,
630+
errorConvention: MetadataErrorType | undefined,
631+
getDynamicParamFromSegment: GetDynamicParamFromSegment,
632+
workStore: WorkStore
633+
) {
634+
const parentParams = {}
635+
const viewportItems: ViewportItems = []
636+
const errorViewportItemRef: ErrorViewportItemRef = {
637+
current: null,
638+
}
639+
const treePrefix = undefined
640+
return resolveViewportItemsImpl(
641+
viewportItems,
642+
tree,
643+
treePrefix,
644+
parentParams,
645+
searchParams,
646+
errorConvention,
647+
errorViewportItemRef,
648+
getDynamicParamFromSegment,
649+
workStore
650+
)
651+
})
652+
653+
async function resolveViewportItemsImpl(
654+
viewportItems: ViewportItems,
655+
tree: LoaderTree,
656+
/** Provided tree can be nested subtree, this argument says what is the path of such subtree */
657+
treePrefix: undefined | string[],
658+
parentParams: Params,
659+
searchParams: Promise<ParsedUrlQuery>,
660+
errorConvention: MetadataErrorType | undefined,
661+
errorViewportItemRef: ErrorViewportItemRef,
662+
getDynamicParamFromSegment: GetDynamicParamFromSegment,
663+
workStore: WorkStore
664+
): Promise<ViewportItems> {
665+
const [segment, parallelRoutes, { page }] = tree
666+
const currentTreePrefix =
667+
treePrefix && treePrefix.length ? [...treePrefix, segment] : [segment]
668+
const isPage = typeof page !== 'undefined'
669+
670+
// Handle dynamic segment params.
671+
const segmentParam = getDynamicParamFromSegment(segment)
672+
/**
673+
* Create object holding the parent params and current params
674+
*/
675+
let currentParams = parentParams
676+
if (segmentParam && segmentParam.value !== null) {
677+
currentParams = {
678+
...parentParams,
679+
[segmentParam.param]: segmentParam.value,
680+
}
681+
}
682+
683+
const params = createServerParamsForMetadata(currentParams, workStore)
684+
685+
let layerProps: LayoutProps | PageProps
686+
if (isPage) {
687+
layerProps = {
688+
params,
689+
searchParams,
690+
}
691+
} else {
692+
layerProps = {
693+
params,
694+
}
695+
}
696+
697+
await collectViewport({
698+
tree,
699+
viewportItems,
700+
errorViewportItemRef,
701+
errorConvention,
702+
props: layerProps,
703+
route: currentTreePrefix
704+
// __PAGE__ shouldn't be shown in a route
705+
.filter((s) => s !== PAGE_SEGMENT_KEY)
706+
.join('/'),
707+
})
708+
709+
for (const key in parallelRoutes) {
710+
const childTree = parallelRoutes[key]
711+
await resolveViewportItemsImpl(
712+
viewportItems,
713+
childTree,
714+
currentTreePrefix,
715+
currentParams,
716+
searchParams,
717+
errorConvention,
718+
errorViewportItemRef,
719+
getDynamicParamFromSegment,
720+
workStore
721+
)
722+
}
723+
724+
if (Object.keys(parallelRoutes).length === 0 && errorConvention) {
725+
// If there are no parallel routes, place error metadata as the last item.
726+
// e.g. layout -> layout -> not-found
727+
viewportItems.push(errorViewportItemRef.current)
728+
}
729+
730+
return viewportItems
731+
}
732+
583733
type WithTitle = { title?: AbsoluteTemplateString | null }
584734
type WithDescription = { description?: string | null }
585735

@@ -692,15 +842,15 @@ function prerenderMetadata(metadataItems: MetadataItems) {
692842
return resolversAndResults
693843
}
694844

695-
function prerenderViewport(metadataItems: MetadataItems) {
845+
function prerenderViewport(viewportItems: ViewportItems) {
696846
// If the index is a function then it is a resolver and the next slot
697847
// is the corresponding result. If the index is not a function it is the result
698848
// itself.
699849
const resolversAndResults: Array<
700850
((value: ResolvedViewport) => void) | Result<Viewport>
701851
> = []
702-
for (let i = 0; i < metadataItems.length; i++) {
703-
const viewportExport = metadataItems[i][2]
852+
for (let i = 0; i < viewportItems.length; i++) {
853+
const viewportExport = viewportItems[i]
704854
getResult(resolversAndResults, viewportExport)
705855
}
706856
return resolversAndResults
@@ -865,11 +1015,11 @@ export async function accumulateMetadata(
8651015
}
8661016

8671017
export async function accumulateViewport(
868-
metadataItems: MetadataItems
1018+
viewportItems: ViewportItems
8691019
): Promise<ResolvedViewport> {
8701020
const resolvedViewport: ResolvedViewport = createDefaultViewport()
8711021

872-
const resolversAndResults = prerenderViewport(metadataItems)
1022+
const resolversAndResults = prerenderViewport(viewportItems)
8731023
let i = 0
8741024

8751025
while (i < resolversAndResults.length) {
@@ -929,14 +1079,14 @@ export async function resolveViewport(
9291079
getDynamicParamFromSegment: GetDynamicParamFromSegment,
9301080
workStore: WorkStore
9311081
): Promise<ResolvedViewport> {
932-
const metadataItems = await resolveMetadataItems(
1082+
const viewportItems = await resolveViewportItems(
9331083
tree,
9341084
searchParams,
9351085
errorConvention,
9361086
getDynamicParamFromSegment,
9371087
workStore
9381088
)
939-
return accumulateViewport(metadataItems)
1089+
return accumulateViewport(viewportItems)
9401090
}
9411091

9421092
function isPromiseLike<T>(

0 commit comments

Comments
 (0)