Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: move resolving metadata process into async Metadata component #48536

Merged
merged 3 commits into from
Apr 18, 2023
Merged
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
42 changes: 27 additions & 15 deletions packages/next/src/lib/metadata/metadata.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,34 +13,46 @@ import {
AppLinksMeta,
} from './generate/opengraph'
import { IconsMetadata } from './generate/icons'
import { accumulateMetadata, MetadataItems } from './resolve-metadata'
import { accumulateMetadata, resolveMetadata } from './resolve-metadata'
import { LoaderTree } from '../../server/lib/app-dir-module'
import { GetDynamicParamFromSegment } from '../../server/app-render/app-render'

// Generate the actual React elements from the resolved metadata.
export async function MetadataTree({
metadata,
tree,
pathname,
searchParams,
getDynamicParamFromSegment,
}: {
metadata: MetadataItems
tree: LoaderTree
pathname: string
allowFallbackMetadataBase: boolean
searchParams: { [key: string]: any }
getDynamicParamFromSegment: GetDynamicParamFromSegment
}) {
const options = {
pathname,
}
const resolved = await accumulateMetadata(metadata, options)
const resolvedMetadata = await resolveMetadata({
tree,
parentParams: {},
metadataItems: [],
searchParams,
getDynamicParamFromSegment,
})
const metadata = await accumulateMetadata(resolvedMetadata, options)

return (
<>
<BasicMetadata metadata={resolved} />
<AlternatesMetadata alternates={resolved.alternates} />
<ItunesMeta itunes={resolved.itunes} />
<FormatDetectionMeta formatDetection={resolved.formatDetection} />
<VerificationMeta verification={resolved.verification} />
<AppleWebAppMeta appleWebApp={resolved.appleWebApp} />
<OpenGraphMetadata openGraph={resolved.openGraph} />
<TwitterMetadata twitter={resolved.twitter} />
<AppLinksMeta appLinks={resolved.appLinks} />
<IconsMetadata icons={resolved.icons} />
<BasicMetadata metadata={metadata} />
<AlternatesMetadata alternates={metadata.alternates} />
<ItunesMeta itunes={metadata.itunes} />
<FormatDetectionMeta formatDetection={metadata.formatDetection} />
<VerificationMeta verification={metadata.verification} />
<AppleWebAppMeta appleWebApp={metadata.appleWebApp} />
<OpenGraphMetadata openGraph={metadata.openGraph} />
<TwitterMetadata twitter={metadata.twitter} />
<AppLinksMeta appLinks={metadata.appLinks} />
<IconsMetadata icons={metadata.icons} />
</>
)
}
74 changes: 70 additions & 4 deletions packages/next/src/lib/metadata/resolve-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
ResolvingMetadata,
} from './types/metadata-interface'
import type { MetadataImageModule } from '../../build/webpack/loaders/metadata/types'
import type { GetDynamicParamFromSegment } from '../../server/app-render/app-render'
import { createDefaultMetadata } from './default-metadata'
import { resolveOpenGraph, resolveTwitter } from './resolvers/resolve-opengraph'
import { resolveTitle } from './resolvers/resolve-title'
Expand All @@ -29,6 +30,7 @@ import { getTracer } from '../../server/lib/trace/tracer'
import { ResolveMetadataSpan } from '../../server/lib/trace/constants'
import { Twitter } from './types/twitter-types'
import { OpenGraph } from './types/opengraph-types'
import { PAGE_SEGMENT_KEY } from '../../shared/lib/constants'

type StaticMetadata = Awaited<ReturnType<typeof resolveStaticMetadata>>

Expand Down Expand Up @@ -263,30 +265,94 @@ async function resolveStaticMetadata(components: ComponentsType, props: any) {

// [layout.metadata, static files metadata] -> ... -> [page.metadata, static files metadata]
export async function collectMetadata({
loaderTree,
tree,
metadataItems: array,
props,
route,
}: {
loaderTree: LoaderTree
tree: LoaderTree
metadataItems: MetadataItems
props: any
route: string
}) {
const [mod, modType] = await getLayoutOrPageModule(loaderTree)
const [mod, modType] = await getLayoutOrPageModule(tree)

if (modType) {
route += `/${modType}`
}

const staticFilesMetadata = await resolveStaticMetadata(loaderTree[2], props)
const staticFilesMetadata = await resolveStaticMetadata(tree[2], props)
const metadataExport = mod
? await getDefinedMetadata(mod, props, route)
: null

array.push([metadataExport, staticFilesMetadata])
}

export async function resolveMetadata({
tree,
parentParams,
metadataItems,
treePrefix = [],
getDynamicParamFromSegment,
searchParams,
}: {
tree: LoaderTree
parentParams: { [key: string]: any }
metadataItems: MetadataItems
/** Provided tree can be nested subtree, this argument says what is the path of such subtree */
treePrefix?: string[]
getDynamicParamFromSegment: GetDynamicParamFromSegment
searchParams: { [key: string]: any }
}): Promise<MetadataItems> {
const [segment, parallelRoutes, { page }] = tree
const currentTreePrefix = [...treePrefix, segment]
const isPage = typeof page !== 'undefined'
// Handle dynamic segment params.
const segmentParam = getDynamicParamFromSegment(segment)
/**
* Create object holding the parent params and current params
*/
const currentParams =
// Handle null case where dynamic param is optional
segmentParam && segmentParam.value !== null
? {
...parentParams,
[segmentParam.param]: segmentParam.value,
}
: // Pass through parent params to children
parentParams

const layerProps = {
params: currentParams,
...(isPage && { searchParams }),
}

await collectMetadata({
tree,
metadataItems,
props: layerProps,
route: currentTreePrefix
// __PAGE__ shouldn't be shown in a route
.filter((s) => s !== PAGE_SEGMENT_KEY)
.join('/'),
})

for (const key in parallelRoutes) {
const childTree = parallelRoutes[key]
await resolveMetadata({
tree: childTree,
metadataItems,
parentParams: currentParams,
treePrefix: currentTreePrefix,
searchParams,
getDynamicParamFromSegment,
})
}

return metadataItems
}

type MetadataAccumulationOptions = {
pathname: string
}
Expand Down
87 changes: 11 additions & 76 deletions packages/next/src/server/app-render/app-render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import type {
import type { StaticGenerationAsyncStorage } from '../../client/components/static-generation-async-storage'
import type { StaticGenerationBailout } from '../../client/components/static-generation-bailout'
import type { RequestAsyncStorage } from '../../client/components/request-async-storage'
import type { MetadataItems } from '../../lib/metadata/resolve-metadata'
// Import builtin react directly to avoid require cache conflicts
import React from 'next/dist/compiled/react'
import ReactDOMServer from 'next/dist/compiled/react-dom/server.browser'
Expand Down Expand Up @@ -44,7 +43,6 @@ import {
import { MetadataTree } from '../../lib/metadata/metadata'
import { RequestAsyncStorageWrapper } from '../async-storage/request-async-storage-wrapper'
import { StaticGenerationAsyncStorageWrapper } from '../async-storage/static-generation-async-storage-wrapper'
import { collectMetadata } from '../../lib/metadata/resolve-metadata'
import { isClientReference } from '../../lib/client-reference'
import { getLayoutOrPageModule, LoaderTree } from '../lib/app-dir-module'
import { isNotFoundError } from '../../client/components/not-found'
Expand Down Expand Up @@ -74,7 +72,6 @@ import {
createFlightRouterStateFromLoaderTree,
} from './create-flight-router-state-from-loader-tree'
import { handleAction } from './action-handler'
import { PAGE_SEGMENT_KEY } from '../../shared/lib/constants'
import { NEXT_DYNAMIC_NO_SSR_CODE } from '../../shared/lib/lazy-dynamic/no-ssr-error'
import { warn } from '../../build/output/log'

Expand Down Expand Up @@ -346,64 +343,6 @@ export async function renderToHTMLOrFlight(
}
}

async function resolveMetadata({
tree,
parentParams,
metadataItems,
treePrefix = [],
}: {
tree: LoaderTree
parentParams: { [key: string]: any }
metadataItems: MetadataItems
/** Provided tree can be nested subtree, this argument says what is the path of such subtree */
treePrefix?: string[]
}): Promise<MetadataItems> {
const [segment, parallelRoutes, { page }] = tree
const currentTreePrefix = [...treePrefix, segment]
const isPage = typeof page !== 'undefined'
// Handle dynamic segment params.
const segmentParam = getDynamicParamFromSegment(segment)
/**
* Create object holding the parent params and current params
*/
const currentParams =
// Handle null case where dynamic param is optional
segmentParam && segmentParam.value !== null
? {
...parentParams,
[segmentParam.param]: segmentParam.value,
}
: // Pass through parent params to children
parentParams

const layerProps = {
params: currentParams,
...(isPage && searchParamsProps),
}

await collectMetadata({
loaderTree: tree,
metadataItems,
props: layerProps,
route: currentTreePrefix
// __PAGE__ shouldn't be shown in a route
.filter((s) => s !== PAGE_SEGMENT_KEY)
.join('/'),
})

for (const key in parallelRoutes) {
const childTree = parallelRoutes[key]
await resolveMetadata({
tree: childTree,
metadataItems,
parentParams: currentParams,
treePrefix: currentTreePrefix,
})
}

return metadataItems
}

let defaultRevalidate: false | undefined | number = false

// Collect all server CSS imports used by this specific entry (or entries, for parallel routes).
Expand Down Expand Up @@ -709,7 +648,8 @@ export async function renderToHTMLOrFlight(

if (
typeof staticGenerationStore.revalidate === 'undefined' ||
staticGenerationStore.revalidate > defaultRevalidate
(typeof staticGenerationStore.revalidate === 'number' &&
staticGenerationStore.revalidate > defaultRevalidate)
) {
staticGenerationStore.revalidate = defaultRevalidate
}
Expand Down Expand Up @@ -1195,11 +1135,6 @@ export async function renderToHTMLOrFlight(
return paths
}

const metadataItems = await resolveMetadata({
tree: loaderTree,
parentParams: {},
metadataItems: [],
})
// Flight data that is going to be passed to the browser.
// Currently a single item array but in the future multiple patches might be combined in a single request.
const flightData: FlightData = (
Expand All @@ -1216,8 +1151,10 @@ export async function renderToHTMLOrFlight(
{/* @ts-expect-error allow to use async server component */}
<MetadataTree
key={requestId}
metadata={metadataItems}
tree={loaderTree}
pathname={pathname}
searchParams={providedSearchParams}
getDynamicParamFromSegment={getDynamicParamFromSegment}
/>
</>
),
Expand Down Expand Up @@ -1292,12 +1229,6 @@ export async function renderToHTMLOrFlight(
}
: {}

const metadataItems = await resolveMetadata({
tree: loaderTree,
parentParams: {},
metadataItems: [],
})

/**
* A new React Component that renders the provided React Component
* using Flight which can then be rendered to HTML.
Expand Down Expand Up @@ -1357,8 +1288,10 @@ export async function renderToHTMLOrFlight(
{/* @ts-expect-error allow to use async server component */}
<MetadataTree
key={requestId}
metadata={metadataItems}
tree={loaderTree}
pathname={pathname}
searchParams={providedSearchParams}
getDynamicParamFromSegment={getDynamicParamFromSegment}
/>
</>
}
Expand Down Expand Up @@ -1562,8 +1495,10 @@ export async function renderToHTMLOrFlight(
{/* @ts-expect-error allow to use async server component */}
<MetadataTree
key={requestId}
metadata={[]}
tree={['', {}, {}]}
pathname={pathname}
searchParams={providedSearchParams}
getDynamicParamFromSegment={getDynamicParamFromSegment}
/>
</head>
<body></body>
Expand Down