diff --git a/packages/next/src/lib/metadata/metadata.tsx b/packages/next/src/lib/metadata/metadata.tsx index c386970a95be2..70c4483221bc3 100644 --- a/packages/next/src/lib/metadata/metadata.tsx +++ b/packages/next/src/lib/metadata/metadata.tsx @@ -16,8 +16,14 @@ import { IconsMetadata } from './generate/icons' import { accumulateMetadata, MetadataItems } from './resolve-metadata' // Generate the actual React elements from the resolved metadata. -export async function MetadataTree({ metadata }: { metadata: MetadataItems }) { - const resolved = await accumulateMetadata(metadata) +export async function MetadataTree({ + metadata, + urlBase, +}: { + metadata: MetadataItems + urlBase: string +}) { + const resolved = await accumulateMetadata(metadata, urlBase) return ( <> diff --git a/packages/next/src/lib/metadata/resolve-metadata.test.ts b/packages/next/src/lib/metadata/resolve-metadata.test.ts index b5a25b256e93b..fb90f3df2ba94 100644 --- a/packages/next/src/lib/metadata/resolve-metadata.test.ts +++ b/packages/next/src/lib/metadata/resolve-metadata.test.ts @@ -9,7 +9,7 @@ describe('accumulateMetadata', () => { [() => Promise.resolve({ description: 'child' }), null], ] - const metadata = await accumulateMetadata(metadataItems) + const metadata = await accumulateMetadata(metadataItems, undefined) expect(metadata).toMatchObject({ description: 'child', }) @@ -23,7 +23,7 @@ describe('accumulateMetadata', () => { [{ title: 'layout' }, null], [{ title: 'page' }, null], ] - const metadata = await accumulateMetadata(metadataItems) + const metadata = await accumulateMetadata(metadataItems, undefined) expect(metadata).toMatchObject({ title: { absolute: 'page', template: null }, }) @@ -43,7 +43,7 @@ describe('accumulateMetadata', () => { [null, null], // same level layout [{ title: 'page' }, null], ] - const metadata = await accumulateMetadata(metadataItems) + const metadata = await accumulateMetadata(metadataItems, undefined) expect(metadata).toMatchObject({ title: { absolute: '2nd parent layout page', template: null }, }) @@ -103,7 +103,8 @@ describe('accumulateMetadata', () => { items.forEach(async (item) => { const [configuredMetadata, result] = item const metadata = await accumulateMetadata( - configuredMetadata.map((m) => [m, null]) + configuredMetadata.map((m) => [m, null]), + undefined ) expect(metadata).toMatchObject(result) }) diff --git a/packages/next/src/lib/metadata/resolve-metadata.ts b/packages/next/src/lib/metadata/resolve-metadata.ts index b15c81428fa61..07d6b906941ab 100644 --- a/packages/next/src/lib/metadata/resolve-metadata.ts +++ b/packages/next/src/lib/metadata/resolve-metadata.ts @@ -52,7 +52,7 @@ function mergeStaticMetadata( card: 'summary_large_image', images: twitter, }, - null + metadata.metadataBase ) metadata.twitter = { ...metadata.twitter, ...resolvedTwitter! } } @@ -62,7 +62,7 @@ function mergeStaticMetadata( { images: opengraph, }, - null + metadata.metadataBase ) metadata.openGraph = { ...metadata.openGraph, ...resolvedOg! } } @@ -81,7 +81,8 @@ function merge( openGraph: string | null } ) { - const metadataBase = source?.metadataBase || null + const metadataBase = source?.metadataBase || target.metadataBase + for (const key_ in source) { const key = key_ as keyof Metadata @@ -166,7 +167,7 @@ function merge( target.other = Object.assign({}, target.other, source.other) break case 'metadataBase': - target.metadataBase = metadataBase + if (metadataBase) target.metadataBase = metadataBase break default: break @@ -239,8 +240,18 @@ export async function collectMetadata( array.push([metadataExport, staticFilesMetadata]) } +function constructMetadataBase(urlBase: string) { + // Use the host of request for metadataBase + try { + return new URL(urlBase) + } catch (_) { + return null + } +} + export async function accumulateMetadata( - metadataItems: MetadataItems + metadataItems: MetadataItems, + urlBase: string | undefined ): Promise { const resolvedMetadata = createDefaultMetadata() @@ -260,6 +271,10 @@ export async function accumulateMetadata( // Loop over all metadata items again, merging synchronously any static object exports, // awaiting any static promise exports, and resolving parent metadata and awaiting any generated metadata + if (urlBase) { + resolvedMetadata.metadataBase = constructMetadataBase(urlBase) + } + let resolvingIndex = 0 for (let i = 0; i < metadataItems.length; i++) { const [metadataExport, staticFilesMetadata] = metadataItems[i] diff --git a/packages/next/src/lib/metadata/resolvers/resolve-opengraph.ts b/packages/next/src/lib/metadata/resolvers/resolve-opengraph.ts index 81619bec09698..3be427f9fcabb 100644 --- a/packages/next/src/lib/metadata/resolvers/resolve-opengraph.ts +++ b/packages/next/src/lib/metadata/resolvers/resolve-opengraph.ts @@ -68,7 +68,16 @@ export function resolveOpenGraph( } } } - resolved.images = resolveAsArrayOrUndefined(og.images) + resolved.images = resolveAsArrayOrUndefined(og.images)?.map((item) => { + if (isStringOrURL(item)) + return { + url: resolveUrl(item, metadataBase)!, + } + else { + item.url = resolveUrl(item.url, metadataBase)! + return item + } + }) } assignProps(openGraph) diff --git a/packages/next/src/server/app-render.tsx b/packages/next/src/server/app-render.tsx index c376b78aacade..5f19b59b580fe 100644 --- a/packages/next/src/server/app-render.tsx +++ b/packages/next/src/server/app-render.tsx @@ -925,6 +925,10 @@ export async function renderToHTMLOrFlight( typeof actionId === 'string' && req.method === 'POST' + const protocol = req.headers['x-forwarded-proto'] || 'http' + const host = req.headers.host + const urlBase = `${protocol}://${host}` + const { buildManifest, subresourceIntegrityManifest, @@ -1813,7 +1817,11 @@ export async function renderToHTMLOrFlight( <> {/* Adding key={requestId} to make metadata remount for each render */} {/* @ts-expect-error allow to use async server component */} - + {resolvedHead} ), @@ -1928,9 +1936,13 @@ export async function renderToHTMLOrFlight( initialTree={initialTree} initialHead={ <> - {/* Adding key={requestId} to make metadata remount for each render */} {/* @ts-expect-error allow to use async server component */} - + {initialHead} } diff --git a/test/e2e/app-dir/metadata/metadata.test.ts b/test/e2e/app-dir/metadata/metadata.test.ts index eb4622ec80c7f..f227c6e042eca 100644 --- a/test/e2e/app-dir/metadata/metadata.test.ts +++ b/test/e2e/app-dir/metadata/metadata.test.ts @@ -450,7 +450,7 @@ createNextDescribe( it('should pick up opengraph-image and twitter-image as static metadata files', async () => { const $ = await next.render$('/opengraph/static') expect($('[property="og:image:url"]').attr('content')).toMatch( - /_next\/static\/media\/metadata\/opengraph-image.\w+.png/ + /http:\/\/\w+:\w+\/_next\/static\/media\/metadata\/opengraph-image.\w+.png/ ) expect($('[property="og:image:type"]').attr('content')).toBe( 'image/png' @@ -459,7 +459,7 @@ createNextDescribe( expect($('[property="og:image:height"]').attr('content')).toBe('114') expect($('[name="twitter:image"]').attr('content')).toMatch( - /_next\/static\/media\/metadata\/twitter-image.\w+.png/ + /http:\/\/\w+:\w+\/_next\/static\/media\/metadata\/twitter-image.\w+.png/ ) expect($('[name="twitter:card"]').attr('content')).toBe( 'summary_large_image'