@@ -68,11 +68,11 @@ type ViewportResolver = (
68
68
69
69
export type MetadataErrorType = 'not-found' | 'forbidden' | 'unauthorized'
70
70
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 >
76
76
77
77
type TitleTemplates = {
78
78
title : string | null
@@ -457,22 +457,65 @@ async function collectMetadata({
457
457
const staticFilesMetadata = await resolveStaticMetadata ( tree [ 2 ] , props )
458
458
const metadataExport = mod ? getDefinedMetadata ( mod , props , { route } ) : null
459
459
460
- const viewportExport = mod ? getDefinedViewport ( mod , props , { route } ) : null
461
-
462
- metadataItems . push ( [ metadataExport , staticFilesMetadata , viewportExport ] )
460
+ metadataItems . push ( [ metadataExport , staticFilesMetadata ] )
463
461
464
462
if ( hasErrorConventionComponent && errorConvention ) {
465
463
const errorMod = await getComponentTypeModule ( tree , errorConvention )
466
- const errorViewportExport = errorMod
467
- ? getDefinedViewport ( errorMod , props , { route } )
468
- : null
469
464
const errorMetadataExport = errorMod
470
465
? getDefinedMetadata ( errorMod , props , { route } )
471
466
: null
472
467
473
468
errorMetadataItem [ 0 ] = errorMetadataExport
474
469
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
476
519
}
477
520
}
478
521
@@ -485,7 +528,7 @@ const resolveMetadataItems = cache(async function (
485
528
) {
486
529
const parentParams = { }
487
530
const metadataItems : MetadataItems = [ ]
488
- const errorMetadataItem : MetadataItems [ number ] = [ null , null , null ]
531
+ const errorMetadataItem : MetadataItems [ number ] = [ null , null ]
489
532
const treePrefix = undefined
490
533
return resolveMetadataItemsImpl (
491
534
metadataItems ,
@@ -580,6 +623,113 @@ async function resolveMetadataItemsImpl(
580
623
return metadataItems
581
624
}
582
625
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
+
583
733
type WithTitle = { title ?: AbsoluteTemplateString | null }
584
734
type WithDescription = { description ?: string | null }
585
735
@@ -692,15 +842,15 @@ function prerenderMetadata(metadataItems: MetadataItems) {
692
842
return resolversAndResults
693
843
}
694
844
695
- function prerenderViewport ( metadataItems : MetadataItems ) {
845
+ function prerenderViewport ( viewportItems : ViewportItems ) {
696
846
// If the index is a function then it is a resolver and the next slot
697
847
// is the corresponding result. If the index is not a function it is the result
698
848
// itself.
699
849
const resolversAndResults : Array <
700
850
( ( value : ResolvedViewport ) => void ) | Result < Viewport >
701
851
> = [ ]
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 ]
704
854
getResult ( resolversAndResults , viewportExport )
705
855
}
706
856
return resolversAndResults
@@ -865,11 +1015,11 @@ export async function accumulateMetadata(
865
1015
}
866
1016
867
1017
export async function accumulateViewport (
868
- metadataItems : MetadataItems
1018
+ viewportItems : ViewportItems
869
1019
) : Promise < ResolvedViewport > {
870
1020
const resolvedViewport : ResolvedViewport = createDefaultViewport ( )
871
1021
872
- const resolversAndResults = prerenderViewport ( metadataItems )
1022
+ const resolversAndResults = prerenderViewport ( viewportItems )
873
1023
let i = 0
874
1024
875
1025
while ( i < resolversAndResults . length ) {
@@ -929,14 +1079,14 @@ export async function resolveViewport(
929
1079
getDynamicParamFromSegment : GetDynamicParamFromSegment ,
930
1080
workStore : WorkStore
931
1081
) : Promise < ResolvedViewport > {
932
- const metadataItems = await resolveMetadataItems (
1082
+ const viewportItems = await resolveViewportItems (
933
1083
tree ,
934
1084
searchParams ,
935
1085
errorConvention ,
936
1086
getDynamicParamFromSegment ,
937
1087
workStore
938
1088
)
939
- return accumulateViewport ( metadataItems )
1089
+ return accumulateViewport ( viewportItems )
940
1090
}
941
1091
942
1092
function isPromiseLike < T > (
0 commit comments