diff --git a/packages/next/src/build/webpack/loaders/next-app-loader.ts b/packages/next/src/build/webpack/loaders/next-app-loader.ts index 82a357af7bade..b6ee93ff9ec23 100644 --- a/packages/next/src/build/webpack/loaders/next-app-loader.ts +++ b/packages/next/src/build/webpack/loaders/next-app-loader.ts @@ -7,7 +7,6 @@ import { sep } from 'path' import { verifyRootLayout } from '../../../lib/verifyRootLayout' import * as Log from '../../../build/output/log' import { APP_DIR_ALIAS } from '../../../lib/constants' -import { resolveFileBasedMetadataForLoader } from '../../../lib/metadata/resolve-metadata' const FILE_TYPES = { layout: 'layout', @@ -37,10 +36,7 @@ async function createTreeCodeFromPath({ resolveParallelSegments, }: { pagePath: string - resolve: ( - pathname: string, - resolveDir?: boolean - ) => Promise + resolve: (pathname: string) => Promise resolveParallelSegments: ( pathname: string ) => [key: string, segment: string][] @@ -48,7 +44,6 @@ async function createTreeCodeFromPath({ const splittedPath = pagePath.split(/[\\/]/) const appDirPrefix = splittedPath[0] const pages: string[] = [] - let rootLayout: string | undefined let globalError: string | undefined @@ -56,7 +51,6 @@ async function createTreeCodeFromPath({ segments: string[] ): Promise<{ treeCode: string - treeMetadataCode: string }> { const segmentPath = segments.join('/') @@ -71,26 +65,12 @@ async function createTreeCodeFromPath({ parallelSegments.push(...resolveParallelSegments(segmentPath)) } - let metadataCode = '' - for (const [parallelKey, parallelSegment] of parallelSegments) { if (parallelSegment === PAGE_SEGMENT) { const matchedPagePath = `${appDirPrefix}${segmentPath}/page` const resolvedPagePath = await resolve(matchedPagePath) if (resolvedPagePath) pages.push(resolvedPagePath) - metadataCode += `{ - type: 'page', - layer: ${ - // There's an extra virtual segment. - segments.length - 1 - }, - mod: () => import(/* webpackMode: "eager" */ ${JSON.stringify( - resolvedPagePath - )}), - path: ${JSON.stringify(resolvedPagePath)}, - },` - // Use '' for segment as it's the page. There can't be a segment called '' so this is the safest way to add it. props[parallelKey] = `['', {}, { page: [() => import(/* webpackMode: "eager" */ ${JSON.stringify( @@ -100,8 +80,9 @@ async function createTreeCodeFromPath({ } const parallelSegmentPath = segmentPath + '/' + parallelSegment - const { treeCode: subtreeCode, treeMetadataCode: subTreeMetadataCode } = - await createSubtreePropsFromSegmentPath([...segments, parallelSegment]) + const { treeCode: subtreeCode } = await createSubtreePropsFromSegmentPath( + [...segments, parallelSegment] + ) // `page` is not included here as it's added above. const filePaths = await Promise.all( @@ -120,27 +101,6 @@ async function createTreeCodeFromPath({ rootLayout = layoutPath } - // Collect metadata for the layout - if (layoutPath) { - metadataCode += `{ - type: 'layout', - layer: ${segments.length}, - mod: () => import(/* webpackMode: "eager" */ ${JSON.stringify( - layoutPath - )}), - path: ${JSON.stringify(layoutPath)}, - },` - } - metadataCode += await resolveFileBasedMetadataForLoader( - segments.length, - (await resolve(`${appDirPrefix}${parallelSegmentPath}/`, true))! - ) - metadataCode += subTreeMetadataCode - - if (!rootLayout) { - rootLayout = layoutPath - } - if (!globalError) { globalError = await resolve( `${appDirPrefix}${parallelSegmentPath}/${GLOBAL_ERROR_FILE_TYPE}` @@ -173,16 +133,13 @@ async function createTreeCodeFromPath({ .map(([key, value]) => `${key}: ${value}`) .join(',\n')} }`, - treeMetadataCode: metadataCode, } } - const { treeCode, treeMetadataCode } = - await createSubtreePropsFromSegmentPath([]) + const { treeCode } = await createSubtreePropsFromSegmentPath([]) return { treeCode: `const tree = ${treeCode}.children;`, - treeMetadataCode: `const metadata = [${treeMetadataCode}];`, - pages: `const pages = ${JSON.stringify(pages)};`, + pages, rootLayout, globalError, } @@ -240,7 +197,7 @@ const nextAppLoader: webpack.LoaderDefinitionFunction<{ const rest = path.slice(pathname.length + 1).split('/') let matchedSegment = rest[0] - // It is the actual page, mark it specially. + // It is the actual page, mark it sepcially. if (rest.length === 1 && matchedSegment === 'page') { matchedSegment = PAGE_SEGMENT } @@ -255,11 +212,7 @@ const nextAppLoader: webpack.LoaderDefinitionFunction<{ return Object.entries(matched) } - const resolver = async (pathname: string, resolveDir?: boolean) => { - if (resolveDir) { - return createAbsolutePath(appDir, pathname) - } - + const resolver = async (pathname: string) => { try { const resolved = await resolve(this.rootContext, pathname) this.addDependency(resolved) @@ -277,17 +230,12 @@ const nextAppLoader: webpack.LoaderDefinitionFunction<{ } } - const { - treeCode, - treeMetadataCode, - pages: pageListCode, - rootLayout, - globalError, - } = await createTreeCodeFromPath({ - pagePath, - resolve: resolver, - resolveParallelSegments, - }) + const { treeCode, pages, rootLayout, globalError } = + await createTreeCodeFromPath({ + pagePath, + resolve: resolver, + resolveParallelSegments, + }) if (!rootLayout) { const errorMessage = `${chalk.bold( @@ -315,8 +263,7 @@ const nextAppLoader: webpack.LoaderDefinitionFunction<{ const result = ` export ${treeCode} - export ${treeMetadataCode} - export ${pageListCode} + export const pages = ${JSON.stringify(pages)} export { default as AppRouter } from 'next/dist/client/components/app-router' export { default as LayoutRouter } from 'next/dist/client/components/layout-router' diff --git a/packages/next/src/build/webpack/plugins/flight-types-plugin.ts b/packages/next/src/build/webpack/plugins/flight-types-plugin.ts index 783b3843cb442..3f41c5818adf1 100644 --- a/packages/next/src/build/webpack/plugins/flight-types-plugin.ts +++ b/packages/next/src/build/webpack/plugins/flight-types-plugin.ts @@ -64,7 +64,6 @@ interface IEntry { ? "runtime?: 'nodejs' | 'experimental-edge' | 'edge'" : '' } - metadata?: any } // ============= diff --git a/packages/next/src/client/components/head.tsx b/packages/next/src/client/components/head.tsx new file mode 100644 index 0000000000000..0b123102d6c7b --- /dev/null +++ b/packages/next/src/client/components/head.tsx @@ -0,0 +1,10 @@ +import React from 'react' + +export function DefaultHead() { + return ( + <> + + + + ) +} diff --git a/packages/next/src/lib/metadata/default-metadata.ts b/packages/next/src/lib/metadata/default-metadata.ts deleted file mode 100644 index c160233a2fd31..0000000000000 --- a/packages/next/src/lib/metadata/default-metadata.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { ResolvedMetadata } from './types/metadata-interface' - -export const createDefaultMetadata = (): ResolvedMetadata => { - return { - viewport: 'width=device-width, initial-scale=1', - - // Other values are all null - metadataBase: null, - title: null, - description: null, - applicationName: null, - authors: null, - generator: null, - keywords: null, - referrer: null, - themeColor: null, - colorScheme: null, - creator: null, - publisher: null, - robots: null, - alternates: { - canonical: null, - languages: {}, - }, - icons: null, - openGraph: null, - twitter: null, - verification: {}, - appleWebApp: null, - formatDetection: null, - itunes: null, - abstract: null, - appLinks: null, - archives: null, - assets: null, - bookmarks: null, - category: null, - classification: null, - other: {}, - } -} diff --git a/packages/next/src/lib/metadata/generate/alternate.tsx b/packages/next/src/lib/metadata/generate/alternate.tsx deleted file mode 100644 index 4bd4eddd148b8..0000000000000 --- a/packages/next/src/lib/metadata/generate/alternate.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import type { ResolvedMetadata } from '../types/metadata-interface' - -import React from 'react' - -export function ResolvedAlternatesMetadata({ - metadata, -}: { - metadata: ResolvedMetadata -}) { - return ( - <> - {metadata.alternates.canonical ? ( - - ) : null} - {Object.entries(metadata.alternates.languages).map(([locale, url]) => - url ? ( - - ) : null - )} - {metadata.alternates.media - ? Object.entries(metadata.alternates.media).map(([media, url]) => - url ? ( - - ) : null - ) - : null} - {metadata.alternates.types - ? Object.entries(metadata.alternates.types).map(([type, url]) => - url ? ( - - ) : null - ) - : null} - - ) -} diff --git a/packages/next/src/lib/metadata/generate/basic.tsx b/packages/next/src/lib/metadata/generate/basic.tsx deleted file mode 100644 index ce8b2f07eaa92..0000000000000 --- a/packages/next/src/lib/metadata/generate/basic.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import type { ResolvedMetadata } from '../types/metadata-interface' - -import React from 'react' -import { Meta } from './utils' - -export function ResolvedBasicMetadata({ - metadata, -}: { - metadata: ResolvedMetadata -}) { - return ( - <> - - {metadata.title !== null ? ( - {metadata.title.absolute} - ) : null} - - - - - - - - - - - - - - {metadata.archives - ? metadata.archives.map((archive) => ( - - )) - : null} - {metadata.assets - ? metadata.assets.map((asset) => ( - - )) - : null} - {metadata.bookmarks - ? metadata.bookmarks.map((bookmark) => ( - - )) - : null} - - - {Object.entries(metadata.other).map(([name, content]) => ( - - ))} - - ) -} diff --git a/packages/next/src/lib/metadata/generate/opengraph.tsx b/packages/next/src/lib/metadata/generate/opengraph.tsx deleted file mode 100644 index 45389ce1dfc4b..0000000000000 --- a/packages/next/src/lib/metadata/generate/opengraph.tsx +++ /dev/null @@ -1,223 +0,0 @@ -import type { ResolvedMetadata } from '../types/metadata-interface' - -import React from 'react' -import { Meta, MultiMeta } from './utils' - -export function ResolvedOpenGraphMetadata({ - openGraph, -}: { - openGraph: ResolvedMetadata['openGraph'] -}) { - if (!openGraph) { - return null - } - - let typedOpenGraph - if ('type' in openGraph) { - switch (openGraph.type) { - case 'website': - typedOpenGraph = - break - case 'article': - typedOpenGraph = ( - <> - - - - - - - - - ) - break - case 'book': - typedOpenGraph = ( - <> - - - - - - - ) - break - case 'profile': - typedOpenGraph = ( - <> - - - - - - - ) - break - case 'music.song': - typedOpenGraph = ( - <> - - - - - - ) - break - case 'music.album': - typedOpenGraph = ( - <> - - - - - - ) - break - case 'music.playlist': - typedOpenGraph = ( - <> - - - - - ) - break - case 'music.radio_station': - typedOpenGraph = ( - <> - - - - ) - break - case 'video.movie': - typedOpenGraph = ( - <> - - - - - - - - - ) - break - case 'video.episode': - typedOpenGraph = ( - <> - - - - - - - - - - ) - break - case 'video.tv_show': - typedOpenGraph = - break - case 'video.other': - typedOpenGraph = - break - default: - throw new Error('Invalid OpenGraph type: ' + (openGraph as any).type) - } - } - - return ( - <> - - - - - - - - - - - - - - - - {typedOpenGraph} - - ) -} diff --git a/packages/next/src/lib/metadata/generate/utils.tsx b/packages/next/src/lib/metadata/generate/utils.tsx deleted file mode 100644 index 6253b4da33eae..0000000000000 --- a/packages/next/src/lib/metadata/generate/utils.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import React from 'react' - -export function Meta({ - name, - property, - content, -}: { - name?: string - property?: string - content: string | number | URL | null | undefined -}): React.ReactElement | null { - if (typeof content !== 'undefined' && content !== null) { - return ( - - ) - } - return null -} - -export function MultiMeta({ - propertyPrefix, - namePrefix, - contents, -}: { - propertyPrefix?: string - namePrefix?: string - contents: - | ( - | Record - | string - | URL - | number - )[] - | null - | undefined -}) { - if (typeof contents === 'undefined' || contents === null) { - return null - } - - const keyPrefix = propertyPrefix || namePrefix - return ( - <> - {contents.map((content, index) => { - if ( - typeof content === 'string' || - typeof content === 'number' || - content instanceof URL - ) { - return ( - - ) - } else { - return ( - - {Object.entries(content).map(([k, v]) => { - return typeof v === 'undefined' ? null : ( - - ) - })} - - ) - } - })} - - ) -} diff --git a/packages/next/src/lib/metadata/resolve-metadata.ts b/packages/next/src/lib/metadata/resolve-metadata.ts deleted file mode 100644 index c012969b6905d..0000000000000 --- a/packages/next/src/lib/metadata/resolve-metadata.ts +++ /dev/null @@ -1,224 +0,0 @@ -import type { - Metadata, - ResolvedMetadata, - ResolvingMetadata, -} from './types/metadata-interface' -import type { Viewport } from './types/extra-types' -import type { ResolvedTwitterMetadata } from './types/twitter-types' -import type { AbsoluteTemplateString } from './types/metadata-types' -import { createDefaultMetadata } from './default-metadata' -import { resolveOpenGraph } from './resolve-opengraph' -import { mergeTitle } from './resolve-title' - -const viewPortKeys = { - width: 'width', - height: 'height', - initialScale: 'initial-scale', - minimumScale: 'minimum-scale', - maximumScale: 'maximum-scale', - viewportFit: 'viewport-fit', -} as const - -type Item = - | { - type: 'layout' | 'page' - // A number that represents which layer or routes that the item is in. Starting from 0. - // Layout and page in the same level will share the same `layer`. - layer: number - mod: () => Promise<{ - metadata?: Metadata - generateMetadata?: ( - props: any, - parent: ResolvingMetadata - ) => Promise - }> - path: string - } - | { - type: 'icon' - // A number that represents which layer the item is in. Starting from 0. - layer: number - mod?: () => Promise<{ - metadata?: Metadata - generateMetadata?: ( - props: any, - parent: ResolvingMetadata - ) => Promise - }> - path?: string - } - -// Merge the source metadata into the resolved target metadata. -function merge( - target: ResolvedMetadata, - source: Metadata, - templateStrings: { - title: string | null - openGraph: string | null - twitter: string | null - } -) { - for (const key_ in source) { - const key = key_ as keyof Metadata - - switch (key) { - case 'other': { - Object.assign(target.other, source.other) - break - } - case 'title': { - if (source.title) { - target.title = source.title as AbsoluteTemplateString - mergeTitle(target, templateStrings.title) - } - break - } - case 'openGraph': { - if (typeof source.openGraph !== 'undefined') { - target.openGraph = resolveOpenGraph(source.openGraph) - if (source.openGraph) { - mergeTitle(target.openGraph, templateStrings.openGraph) - } - } else { - target.openGraph = null - } - break - } - case 'twitter': { - if (source.twitter) { - target.twitter = source.twitter as ResolvedTwitterMetadata - mergeTitle(target.twitter, templateStrings.twitter) - } else { - target.twitter = null - } - break - } - case 'viewport': { - let content: string | null = null - const { viewport } = source - if (typeof viewport === 'string') { - content = viewport - } else if (viewport) { - content = '' - for (const viewportKey_ in viewPortKeys) { - const viewportKey = viewportKey_ as keyof Viewport - if (viewport[viewportKey]) { - if (content) content += ', ' - content += `${viewPortKeys[viewportKey]}=${viewport[viewportKey]}` - } - } - } - target.viewport = content - break - } - default: { - // TODO: Make sure the type is correct. - // @ts-ignore - target[key] = source[key] - break - } - } - } -} - -export async function resolveMetadata(metadataItems: Item[]) { - const resolvedMetadata = createDefaultMetadata() - - let committedTitleTemplate: string | null = null - let committedOpenGraphTitleTemplate: string | null = null - let committedTwitterTitleTemplate: string | null = null - - let lastLayer = 0 - // from root layout to page metadata - for (let i = 0; i < metadataItems.length; i++) { - const item = metadataItems[i] - const isLayout = item.type === 'layout' - const isPage = item.type === 'page' - if (isLayout || isPage) { - let layerMod = await item.mod() - - // Layer is a client component, we just skip it. It can't have metadata - // exported. Note that during our SWC transpilation, it should check if - // the exports are valid and give specific error messages. - if ( - '$$typeof' in layerMod && - (layerMod as any).$$typeof === Symbol.for('react.module.reference') - ) { - continue - } - - if (layerMod.metadata && layerMod.generateMetadata) { - throw new Error( - `A ${item.type} is exporting both metadata and generateMetadata which is not supported. If all of the metadata you want to associate to this ${item.type} is static use the metadata export, otherwise use generateMetadata. File: ` + - item.path - ) - } - - // If we resolved all items in this layer, commit the stashed titles. - if (item.layer >= lastLayer) { - committedTitleTemplate = resolvedMetadata.title?.template || null - committedOpenGraphTitleTemplate = - resolvedMetadata.openGraph?.title?.template || null - committedTwitterTitleTemplate = - resolvedMetadata.twitter?.title?.template || null - - lastLayer = item.layer - } - - if (layerMod.metadata) { - merge(resolvedMetadata, layerMod.metadata, { - title: committedTitleTemplate, - openGraph: committedOpenGraphTitleTemplate, - twitter: committedTwitterTitleTemplate, - }) - } else if (layerMod.generateMetadata) { - merge( - resolvedMetadata, - await layerMod.generateMetadata( - // TODO: Rewrite this to pass correct params and resolving metadata value. - {}, - Promise.resolve(resolvedMetadata) - ), - { - title: committedTitleTemplate, - openGraph: committedOpenGraphTitleTemplate, - twitter: committedTwitterTitleTemplate, - } - ) - } - } - } - - return resolvedMetadata -} - -// TODO: Implement this function. -export async function resolveFileBasedMetadataForLoader( - _layer: number, - _dir: string -) { - let metadataCode = '' - - // const files = await fs.readdir(path.normalize(dir)) - // for (const file of files) { - // // TODO: Get a full list and filter out directories. - // if (file === 'icon.svg') { - // metadataCode += `{ - // type: 'icon', - // layer: ${layer}, - // path: ${JSON.stringify(path.join(dir, file))}, - // },` - // } else if (file === 'icon.jsx') { - // metadataCode += `{ - // type: 'icon', - // layer: ${layer}, - // mod: () => import(/* webpackMode: "eager" */ ${JSON.stringify( - // path.join(dir, file) - // )}), - // path: ${JSON.stringify(path.join(dir, file))}, - // },` - // } - // } - - return metadataCode -} diff --git a/packages/next/src/lib/metadata/resolve-opengraph.ts b/packages/next/src/lib/metadata/resolve-opengraph.ts deleted file mode 100644 index c322baaa32536..0000000000000 --- a/packages/next/src/lib/metadata/resolve-opengraph.ts +++ /dev/null @@ -1,92 +0,0 @@ -import type { Metadata } from './types/metadata-interface' -import type { - OpenGraphType, - OpenGraph, - ResolvedOpenGraph, -} from './types/opengraph-types' - -const OgTypFields = { - article: ['authors', 'tags'], - song: ['albums', 'musicians'], - playlist: ['albums', 'musicians'], - radio: ['creators'], - video: ['actors', 'directors', 'writers', 'tags'], - basic: [ - 'emails', - 'phoneNumbers', - 'faxNumbers', - 'alternateLocale', - 'images', - 'audio', - 'videos', - ], -} as const - -function resolveAsArrayOrUndefined(value: T): undefined | any[] { - if (typeof value === 'undefined' || value === null) { - return undefined - } - if (Array.isArray(value)) { - return value - } - return [value] -} - -function getFieldsByOgType(ogType: OpenGraphType | undefined) { - switch (ogType) { - case 'article': - case 'book': - return OgTypFields.article - case 'music.song': - case 'music.album': - return OgTypFields.song - case 'music.playlist': - return OgTypFields.playlist - case 'music.radio_station': - return OgTypFields.radio - case 'video.movie': - case 'video.episode': - return OgTypFields.video - default: - return OgTypFields.basic - } -} - -export function resolveOpenGraph( - openGraph: Metadata['openGraph'] -): ResolvedOpenGraph { - const url = openGraph - ? typeof openGraph.url === 'string' - ? new URL(openGraph.url) - : openGraph.url - : undefined - - // TODO: improve typing - const resolved: { [x: string]: any } = openGraph || {} - - function assignProps(og: OpenGraph) { - const ogType = og && 'type' in og ? og.type : undefined - const keys = getFieldsByOgType(ogType) - for (const k of keys) { - const key = k as keyof OpenGraph - if (key in og) { - // TODO: fix typing inferring - // @ts-ignore - const value = resolveAsArrayOrUndefined(og[key]) - if (value != null) { - ;(resolved as any)[key] = value - } - } - } - } - - if (openGraph) { - assignProps(openGraph) - } - - if (url) { - resolved.url = url - } - - return resolved as ResolvedOpenGraph -} diff --git a/packages/next/src/lib/metadata/resolve-title.ts b/packages/next/src/lib/metadata/resolve-title.ts deleted file mode 100644 index fae87a60b25b7..0000000000000 --- a/packages/next/src/lib/metadata/resolve-title.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { Metadata } from './types/metadata-interface' -import type { AbsoluteTemplateString } from './types/metadata-types' - -function resolveTitleTemplate(template: string | null, title: string) { - return template ? template.replace(/%s/g, title) : title -} - -export function mergeTitle( - source: T, - stashedTemplate: string | null -) { - const { title } = source - - let resolved - const template = - typeof source.title !== 'string' && - source.title && - 'template' in source.title - ? source.title.template - : null - - if (typeof title === 'string') { - resolved = resolveTitleTemplate(stashedTemplate, title) - } else if (title) { - if ('default' in title) { - resolved = resolveTitleTemplate(stashedTemplate, title.default) - } - if ('absolute' in title && title.absolute) { - resolved = title.absolute - } - } - - const target = source - if (source.title && typeof source.title !== 'string') { - const targetTitle = source.title as AbsoluteTemplateString - targetTitle.template = template - targetTitle.absolute = resolved || '' - } else { - target.title = { absolute: resolved || source.title || '', template } - } -} diff --git a/packages/next/src/lib/metadata/types/alternative-urls-types.ts b/packages/next/src/lib/metadata/types/alternative-urls-types.ts deleted file mode 100644 index ad4938df9feb5..0000000000000 --- a/packages/next/src/lib/metadata/types/alternative-urls-types.ts +++ /dev/null @@ -1,261 +0,0 @@ -// Reference: https://hreflang.org/what-is-a-valid-hreflang - -type LangCode = - | 'af-ZA' - | 'am-ET' - | 'ar-AE' - | 'ar-BH' - | 'ar-DZ' - | 'ar-EG' - | 'ar-IQ' - | 'ar-JO' - | 'ar-KW' - | 'ar-LB' - | 'ar-LY' - | 'ar-MA' - | 'arn-CL' - | 'ar-OM' - | 'ar-QA' - | 'ar-SA' - | 'ar-SD' - | 'ar-SY' - | 'ar-TN' - | 'ar-YE' - | 'as-IN' - | 'az-az' - | 'az-Cyrl-AZ' - | 'az-Latn-AZ' - | 'ba-RU' - | 'be-BY' - | 'bg-BG' - | 'bn-BD' - | 'bn-IN' - | 'bo-CN' - | 'br-FR' - | 'bs-Cyrl-BA' - | 'bs-Latn-BA' - | 'ca-ES' - | 'co-FR' - | 'cs-CZ' - | 'cy-GB' - | 'da-DK' - | 'de-AT' - | 'de-CH' - | 'de-DE' - | 'de-LI' - | 'de-LU' - | 'dsb-DE' - | 'dv-MV' - | 'el-CY' - | 'el-GR' - | 'en-029' - | 'en-AU' - | 'en-BZ' - | 'en-CA' - | 'en-cb' - | 'en-GB' - | 'en-IE' - | 'en-IN' - | 'en-JM' - | 'en-MT' - | 'en-MY' - | 'en-NZ' - | 'en-PH' - | 'en-SG' - | 'en-TT' - | 'en-US' - | 'en-ZA' - | 'en-ZW' - | 'es-AR' - | 'es-BO' - | 'es-CL' - | 'es-CO' - | 'es-CR' - | 'es-DO' - | 'es-EC' - | 'es-ES' - | 'es-GT' - | 'es-HN' - | 'es-MX' - | 'es-NI' - | 'es-PA' - | 'es-PE' - | 'es-PR' - | 'es-PY' - | 'es-SV' - | 'es-US' - | 'es-UY' - | 'es-VE' - | 'et-EE' - | 'eu-ES' - | 'fa-IR' - | 'fi-FI' - | 'fil-PH' - | 'fo-FO' - | 'fr-BE' - | 'fr-CA' - | 'fr-CH' - | 'fr-FR' - | 'fr-LU' - | 'fr-MC' - | 'fy-NL' - | 'ga-IE' - | 'gd-GB' - | 'gd-ie' - | 'gl-ES' - | 'gsw-FR' - | 'gu-IN' - | 'ha-Latn-NG' - | 'he-IL' - | 'hi-IN' - | 'hr-BA' - | 'hr-HR' - | 'hsb-DE' - | 'hu-HU' - | 'hy-AM' - | 'id-ID' - | 'ig-NG' - | 'ii-CN' - | 'in-ID' - | 'is-IS' - | 'it-CH' - | 'it-IT' - | 'iu-Cans-CA' - | 'iu-Latn-CA' - | 'iw-IL' - | 'ja-JP' - | 'ka-GE' - | 'kk-KZ' - | 'kl-GL' - | 'km-KH' - | 'kn-IN' - | 'kok-IN' - | 'ko-KR' - | 'ky-KG' - | 'lb-LU' - | 'lo-LA' - | 'lt-LT' - | 'lv-LV' - | 'mi-NZ' - | 'mk-MK' - | 'ml-IN' - | 'mn-MN' - | 'mn-Mong-CN' - | 'moh-CA' - | 'mr-IN' - | 'ms-BN' - | 'ms-MY' - | 'mt-MT' - | 'nb-NO' - | 'ne-NP' - | 'nl-BE' - | 'nl-NL' - | 'nn-NO' - | 'no-no' - | 'nso-ZA' - | 'oc-FR' - | 'or-IN' - | 'pa-IN' - | 'pl-PL' - | 'prs-AF' - | 'ps-AF' - | 'pt-BR' - | 'pt-PT' - | 'qut-GT' - | 'quz-BO' - | 'quz-EC' - | 'quz-PE' - | 'rm-CH' - | 'ro-mo' - | 'ro-RO' - | 'ru-mo' - | 'ru-RU' - | 'rw-RW' - | 'sah-RU' - | 'sa-IN' - | 'se-FI' - | 'se-NO' - | 'se-SE' - | 'si-LK' - | 'sk-SK' - | 'sl-SI' - | 'sma-NO' - | 'sma-SE' - | 'smj-NO' - | 'smj-SE' - | 'smn-FI' - | 'sms-FI' - | 'sq-AL' - | 'sr-BA' - | 'sr-CS' - | 'sr-Cyrl-BA' - | 'sr-Cyrl-CS' - | 'sr-Cyrl-ME' - | 'sr-Cyrl-RS' - | 'sr-Latn-BA' - | 'sr-Latn-CS' - | 'sr-Latn-ME' - | 'sr-Latn-RS' - | 'sr-ME' - | 'sr-RS' - | 'sr-sp' - | 'sv-FI' - | 'sv-SE' - | 'sw-KE' - | 'syr-SY' - | 'ta-IN' - | 'te-IN' - | 'tg-Cyrl-TJ' - | 'th-TH' - | 'tk-TM' - | 'tlh-QS' - | 'tn-ZA' - | 'tr-TR' - | 'tt-RU' - | 'tzm-Latn-DZ' - | 'ug-CN' - | 'uk-UA' - | 'ur-PK' - | 'uz-Cyrl-UZ' - | 'uz-Latn-UZ' - | 'uz-uz' - | 'vi-VN' - | 'wo-SN' - | 'xh-ZA' - | 'yo-NG' - | 'zh-CN' - | 'zh-HK' - | 'zh-MO' - | 'zh-SG' - | 'zh-TW' - | 'zu-ZA' - -type UnmatchedLang = 'x-default' - -type HrefLang = LangCode | UnmatchedLang - -type Languages = { - [s in HrefLang]?: T -} - -export type AlternateURLs = { - canonical?: null | string | URL - languages?: Languages - media?: { - [media: string]: null | string | URL - } - types?: { - [types: string]: null | string | URL - } -} - -export type ResolvedAlternateURLs = { - canonical: null | URL - languages: Languages - media?: { - [media: string]: null | URL - } - types?: { - [types: string]: null | URL - } -} diff --git a/packages/next/src/lib/metadata/types/extra-types.ts b/packages/next/src/lib/metadata/types/extra-types.ts deleted file mode 100644 index 51001e695d5f2..0000000000000 --- a/packages/next/src/lib/metadata/types/extra-types.ts +++ /dev/null @@ -1,84 +0,0 @@ -// When rendering applink meta tags add a namespace tag before each array instance -// if more than one member exists. -// ref: https://developers.facebook.com/docs/applinks/metadata-reference - -export type AppLinks = { - ios?: AppLinksApple | Array - iphone?: AppLinksApple | Array - ipad?: AppLinksApple | Array - android?: AppLinksAndroid | Array - windows_phone?: AppLinksWindows | Array - windows?: AppLinksWindows | Array - windows_universal?: AppLinksWindows | Array - web?: AppLinksWeb | Array -} -export type AppLinksApple = { - url: string | URL - app_store_id?: string | number - app_name?: string -} -export type AppLinksAndroid = { - package: string - url?: string | URL - class?: string - app_name?: string -} -export type AppLinksWindows = { - url: string | URL - app_id?: string - app_name?: string -} -export type AppLinksWeb = { - url: string | URL - should_fallback?: boolean -} - -// Apple Itunes APp -// https://developer.apple.com/documentation/webkit/promoting_apps_with_smart_app_banners -export type ItunesApp = { - appId: string - appArgument?: string -} - -// Viewport meta structure -// https://developer.mozilla.org/en-US/docs/Web/HTML/Viewport_meta_tag -// intentionally leaving out user-scalable, use a string if you want that behavior -export type Viewport = { - width?: string | number - height?: string | number - initialScale?: number - minimumScale?: number - maximumScale?: number -} - -// Apple Web App -// https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariHTMLRef/Articles/MetaTags.html -// https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariWebContent/ConfiguringWebApplications/ConfiguringWebApplications.html -export type AppleWebApp = { - // default true - capable?: boolean - title?: string - startupImage?: AppleImage | Array - // default "default" - statusBarStyle?: 'default' | 'black' | 'black-translucent' -} -export type AppleImage = string | AppleImageDescriptor -export type AppleImageDescriptor = { - url: string - media?: string -} - -// Format Detection -// This is a poorly specified metadata export type that is supposed to -// control whether the device attempts to conver text that matches -// certain formats into links for action. The most supported example -// is how mobile devices detect phone numbers and make them into links -// that can initiate a phone call -// https://www.goodemailcode.com/email-code/template.html -export type FormatDetection = { - telephone?: boolean - date?: boolean - address?: boolean - email?: boolean - url?: boolean -} diff --git a/packages/next/src/lib/metadata/types/manifest-types.ts b/packages/next/src/lib/metadata/types/manifest-types.ts deleted file mode 100644 index aa972679d4ae8..0000000000000 --- a/packages/next/src/lib/metadata/types/manifest-types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export type Manifest = { - // fill this out -} diff --git a/packages/next/src/lib/metadata/types/metadata-interface.ts b/packages/next/src/lib/metadata/types/metadata-interface.ts deleted file mode 100644 index 6b07373019607..0000000000000 --- a/packages/next/src/lib/metadata/types/metadata-interface.ts +++ /dev/null @@ -1,204 +0,0 @@ -import type { - AlternateURLs, - ResolvedAlternateURLs, -} from './alternative-urls-types' -import type { - AppleWebApp, - AppLinks, - FormatDetection, - ItunesApp, - Viewport, -} from './extra-types' -import type { - AbsoluteTemplateString, - Author, - ColorSchemeEnum, - Icon, - Icons, - ReferrerEnum, - Robots, - TemplateString, - Verification, -} from './metadata-types' -import type { OpenGraph, ResolvedOpenGraph } from './opengraph-types' -import { ResolvedTwitterMetadata, Twitter } from './twitter-types' - -export interface Metadata { - // origin and base path for absolute urls for various metadata links such as - // opengraph-image - metadataBase: null | URL - - // The Document title - title?: null | string | TemplateString - - // The Document description, and optionally the opengraph and twitter descriptions - description?: null | string - - // Standard metadata names - // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name - applicationName?: null | string | Array - authors?: null | Author | Array - generator?: null | string - // if you provide an array it will be flattened into a single tag with comma separation - keywords?: null | string | Array - referrer?: null | ReferrerEnum - themeColor?: null | string - colorScheme?: null | ColorSchemeEnum - viewport?: null | string | Viewport - creator?: null | string - publisher?: null | string - - // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name#other_metadata_names - robots?: null | string | Robots - - // The canonical and alternate URLs for this location - alternates: AlternateURLs - - // Defaults to rel="icon" but the Icons type can be used - // to get more specific about rel types - icons?: null | Array | Icons - - openGraph?: null | OpenGraph - - twitter?: null | Twitter - - // common verification tokens - verification?: Verification - - // Apple web app metadata - // https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariHTMLRef/Articles/MetaTags.html - appleWebApp?: null | boolean | AppleWebApp - - // Should devices try to interpret various formats and make actionable links - // out of them? The canonical example is telephone numbers on mobile that can - // be clicked to dial - formatDetection?: null | FormatDetection - - // meta name="apple-itunes-app" - itunes?: null | ItunesApp - - // meta name="abstract" - // A brief description of what this web-page is about. - // Not recommended, superceded by description. - // https://www.metatags.org/all-meta-tags-overview/meta-name-abstract/ - abstract?: null | string - - // Facebook AppLinks - appLinks?: null | AppLinks - - // link rel properties - archives?: null | string | Array - assets?: null | string | Array - bookmarks?: null | string | Array // This is technically against HTML spec but is used in wild - - // meta name properties - category?: null | string - classification?: null | string - - // Arbitrary name/value pairs - other?: { - [name: string]: string | number | Array - } - - /** - * Deprecated options that have a preferred method - * */ - // Use appWebApp to configure apple-mobile-web-app-capable which provides - // https://www.appsloveworld.com/coding/iphone/11/difference-between-apple-mobile-web-app-capable-and-apple-touch-fullscreen-ipho - 'apple-touch-fullscreen'?: never - - // Obsolete since iOS 7. use icons.apple or "app-touch-icon" instead - // https://web.dev/apple-touch-icon/ - 'apple-touch-icon-precomposed'?: never -} - -export interface ResolvedMetadata { - // origin and base path for absolute urls for various metadata links such as - // opengraph-image - metadataBase: null | URL - - // The Document title and template if defined - title: null | AbsoluteTemplateString - - // The Document description, and optionally the opengraph and twitter descriptions - description: null | string - - // Standard metadata names - // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name - applicationName: null | string - authors: null | Array - generator: null | string - // if you provide an array it will be flattened into a single tag with comma separation - keywords: null | Array - referrer: null | ReferrerEnum - themeColor: null | string - colorScheme: null | ColorSchemeEnum - viewport: null | string - creator: null | string - publisher: null | string - - // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name#other_metadata_names - robots: null | string - - // The canonical and alternate URLs for this location - alternates: ResolvedAlternateURLs - - // Defaults to rel="icon" but the Icons type can be used - // to get more specific about rel types - icons: null | Icons - - openGraph: null | ResolvedOpenGraph - - twitter: null | ResolvedTwitterMetadata - - // common verification tokens - verification: Verification - - // Apple web app metadata - // https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariHTMLRef/Articles/MetaTags.html - appleWebApp: null | AppleWebApp - - // Should devices try to interpret various formats and make actionable links - // out of them? The canonical example is telephone numbers on mobile that can - // be clicked to dial - formatDetection: null | FormatDetection - - // meta name="apple-itunes-app" - itunes: null | ItunesApp - - // meta name="abstract" - // A brief description of what this web-page is about. - // Not recommended, superceded by description. - // https://www.metatags.org/all-meta-tags-overview/meta-name-abstract/ - abstract: null | string - - // Facebook AppLinks - appLinks: null | AppLinks - - // link rel properties - archives: null | Array - assets: null | Array - bookmarks: null | Array // This is technically against HTML spec but is used in wild - - // meta name properties - category: null | string - classification: null | string - - // Arbitrary name/value pairs - other: { - [name: string]: string | number | Array - } - - /** - * Deprecated options that have a preferred method - * */ - // Use appWebApp to configure apple-mobile-web-app-capable which provides - // https://www.appsloveworld.com/coding/iphone/11/difference-between-apple-mobile-web-app-capable-and-apple-touch-fullscreen-ipho - 'apple-touch-fullscreen'?: never - - // Obsolete since iOS 7. use icons.apple or "app-touch-icon" instead - // https://web.dev/apple-touch-icon/ - 'apple-touch-icon-precomposed'?: never -} - -export type ResolvingMetadata = Promise diff --git a/packages/next/src/lib/metadata/types/metadata-types.ts b/packages/next/src/lib/metadata/types/metadata-types.ts deleted file mode 100644 index fbd3b502cae68..0000000000000 --- a/packages/next/src/lib/metadata/types/metadata-types.ts +++ /dev/null @@ -1,93 +0,0 @@ -/** - * - * Metadata types - * - */ -export type TemplateString = - | DefaultTemplateString - | AbsoluteTemplateString - | AbsoluteString -export type DefaultTemplateString = { - default: string - template: string -} -export type AbsoluteTemplateString = { - absolute: string - template: string | null -} -export type AbsoluteString = { - absolute: string -} - -export type Author = { - // renders as - // rel="shortcut icon" - shortcut?: Icon | Array - // rel="apple-touch-icon" - apple?: Icon | Array - // rel inferred from descriptor, defaults to "icon" - other?: Icon | Array -} - -export type Verification = { - google?: null | string | number | Array - yahoo?: null | string | number | Array - // if you ad-hoc additional verification - other?: { - [name: string]: string | number | Array - } -} diff --git a/packages/next/src/lib/metadata/types/opengraph-types.ts b/packages/next/src/lib/metadata/types/opengraph-types.ts deleted file mode 100644 index 3b90c1c87874d..0000000000000 --- a/packages/next/src/lib/metadata/types/opengraph-types.ts +++ /dev/null @@ -1,267 +0,0 @@ -import type { AbsoluteTemplateString, TemplateString } from './metadata-types' - -export type OpenGraphType = - | 'article' - | 'book' - | 'music.song' - | 'music.album' - | 'music.playlist' - | 'music.radio_station' - | 'profile' - | 'website' - | 'video.tv_show' - | 'video.other' - | 'video.movie' - | 'video.episode' - -export type OpenGraph = - | OpenGraphWebsite - | OpenGraphArticle - | OpenGraphBook - | OpenGraphProfile - | OpenGraphMusicSong - | OpenGraphMusicAlbum - | OpenGraphMusicPlaylist - | OpenGraphRadioStation - | OpenGraphVideoMovie - | OpenGraphVideoEpisode - | OpenGraphVideoTVShow - | OpenGraphVideoOther - | OpenGraphMetadata - -// update this type to reflect actual locales -type Locale = string - -type OpenGraphMetadata = { - determiner?: 'a' | 'an' | 'the' | 'auto' | '' - title?: TemplateString - description?: string - emails?: string | Array - phoneNumbers?: string | Array - faxNumbers?: string | Array - siteName?: string - locale?: Locale - alternateLocale?: Locale | Array - images?: OGImage | Array - audio?: OGAudio | Array - videos?: OGVideo | Array - url?: string | URL - countryName?: string - ttl?: number -} -type OpenGraphWebsite = OpenGraphMetadata & { - type: 'website' -} -type OpenGraphArticle = OpenGraphMetadata & { - type: 'article' - publishedTime?: string // datetime - modifiedTime?: string // datetime - expirationTime?: string // datetime - authors?: null | string | URL | Array - section?: null | string - tags?: null | string | Array -} -type OpenGraphBook = OpenGraphMetadata & { - type: 'book' - isbn?: null | string - releaseDate?: null | string // datetime - authors?: null | string | URL | Array - tags?: null | string | Array -} -type OpenGraphProfile = OpenGraphMetadata & { - type: 'profile' - firstName?: null | string - lastName?: null | string - username?: null | string - gender?: null | string -} -type OpenGraphMusicSong = OpenGraphMetadata & { - type: 'music.song' - duration?: null | number - albums?: null | string | URL | OGAlbum | Array - musicians?: null | string | URL | Array -} -type OpenGraphMusicAlbum = OpenGraphMetadata & { - type: 'music.album' - songs?: null | string | URL | OGSong | Array - musicians?: null | string | URL | Array - releaseDate?: null | string // datetime -} -type OpenGraphMusicPlaylist = OpenGraphMetadata & { - type: 'music.playlist' - songs?: null | string | URL | OGSong | Array - creators?: null | string | URL | Array -} -type OpenGraphRadioStation = OpenGraphMetadata & { - type: 'music.radio_station' - creators?: null | string | URL | Array -} -type OpenGraphVideoMovie = OpenGraphMetadata & { - type: 'video.movie' - actors?: null | string | URL | OGActor | Array - directors?: null | string | URL | Array - writers?: null | string | URL | Array - duration?: null | number - releaseDate?: null | string // datetime - tags?: null | string | Array -} -type OpenGraphVideoEpisode = OpenGraphMetadata & { - type: 'video.episode' - actors?: null | string | URL | OGActor | Array - directors?: null | string | URL | Array - writers?: null | string | URL | Array - duration?: null | number - releaseDate?: null | string // datetime - tags?: null | string | Array - series?: null | string | URL -} -type OpenGraphVideoTVShow = OpenGraphMetadata & { - type: 'video.tv_show' -} -type OpenGraphVideoOther = OpenGraphMetadata & { - type: 'video.other' -} - -type OGImage = string | OGImageDescriptor | URL -type OGImageDescriptor = { - url: string | URL - secureUrl?: string | URL - alt?: string - type?: string - width?: string | number - height?: string | number -} -type OGAudio = string | OGAudioDescriptor | URL -type OGAudioDescriptor = { - url: string | URL - secure_url?: string | URL - type?: string -} -type OGVideo = string | OGVideoDescriptor | URL -type OGVideoDescriptor = { - url: string | URL - secureUrl?: string | URL - type?: string - width?: string | number - height?: string | number -} - -export type ResolvedOpenGraph = - | ResolvedOpenGraphWebsite - | ResolvedOpenGraphArticle - | ResolvedOpenGraphBook - | ResolvedOpenGraphProfile - | ResolvedOpenGraphMusicSong - | ResolvedOpenGraphMusicAlbum - | ResolvedOpenGraphMusicPlaylist - | ResolvedOpenGraphRadioStation - | ResolvedOpenGraphVideoMovie - | ResolvedOpenGraphVideoEpisode - | ResolvedOpenGraphVideoTVShow - | ResolvedOpenGraphVideoOther - | ResolvedOpenGraphMetadata - -type ResolvedOpenGraphMetadata = { - determiner?: 'a' | 'an' | 'the' | 'auto' | '' - title?: AbsoluteTemplateString - description?: string - emails?: Array - phoneNumbers?: Array - faxNumbers?: Array - siteName?: string - locale?: Locale - alternateLocale?: Array - images?: Array - audio?: Array - videos?: Array - url?: URL - countryName?: string - ttl?: number -} -type ResolvedOpenGraphWebsite = ResolvedOpenGraphMetadata & { - type: 'website' -} -type ResolvedOpenGraphArticle = ResolvedOpenGraphMetadata & { - type: 'article' - publishedTime?: string // datetime - modifiedTime?: string // datetime - expirationTime?: string // datetime - authors?: Array - section?: string - tags?: Array -} -type ResolvedOpenGraphBook = ResolvedOpenGraphMetadata & { - type: 'book' - isbn?: string - releaseDate?: string // datetime - authors?: Array - tags?: Array -} -type ResolvedOpenGraphProfile = ResolvedOpenGraphMetadata & { - type: 'profile' - firstName?: string - lastName?: string - username?: string - gender?: string -} -type ResolvedOpenGraphMusicSong = ResolvedOpenGraphMetadata & { - type: 'music.song' - duration?: number - albums?: Array - musicians?: Array -} -type ResolvedOpenGraphMusicAlbum = ResolvedOpenGraphMetadata & { - type: 'music.album' - songs?: Array - musicians?: Array - releaseDate?: string // datetime -} -type ResolvedOpenGraphMusicPlaylist = ResolvedOpenGraphMetadata & { - type: 'music.playlist' - songs?: Array - creators?: Array -} -type ResolvedOpenGraphRadioStation = ResolvedOpenGraphMetadata & { - type: 'music.radio_station' - creators?: Array -} -type ResolvedOpenGraphVideoMovie = ResolvedOpenGraphMetadata & { - type: 'video.movie' - actors?: Array - directors?: Array - writers?: Array - duration?: number - releaseDate?: string // datetime - tags?: Array -} -type ResolvedOpenGraphVideoEpisode = ResolvedOpenGraphMetadata & { - type: 'video.episode' - actors?: Array - directors?: Array - writers?: Array - duration?: number - releaseDate?: string // datetime - tags?: Array - series?: string | URL -} -type ResolvedOpenGraphVideoTVShow = ResolvedOpenGraphMetadata & { - type: 'video.tv_show' -} -type ResolvedOpenGraphVideoOther = ResolvedOpenGraphMetadata & { - type: 'video.other' -} - -type OGSong = { - url: string | URL - disc?: number - track?: number -} -type OGAlbum = { - url: string | URL - disc?: number - track?: number -} -type OGActor = { - url: string | URL - role?: string -} diff --git a/packages/next/src/lib/metadata/types/twitter-types.ts b/packages/next/src/lib/metadata/types/twitter-types.ts deleted file mode 100644 index f0666d9f64ebd..0000000000000 --- a/packages/next/src/lib/metadata/types/twitter-types.ts +++ /dev/null @@ -1,66 +0,0 @@ -import type { AbsoluteTemplateString, TemplateString } from './metadata-types' - -export type Twitter = - | TwitterSummary - | TwitterSummaryLargeImage - | TwitterPlayer - | TwitterApp - | TwitterMetadata - -type TwitterMetadata = { - // defaults to card="summary" - site?: string // username for account associated to the site itself - siteId?: string // id for account associated to the site itself - creator?: string // username for the account associated to the creator of the content on the site - creatorId?: string // id for the account associated to the creator of the content on the site - title?: string | TemplateString - description?: string - images?: TwitterImage | Array -} -type TwitterSummary = TwitterMetadata & { - card: 'summary' -} -type TwitterSummaryLargeImage = TwitterMetadata & { - card: 'summary_large_image' -} -type TwitterPlayer = TwitterMetadata & { - card: 'player' - players: TwitterPlayerDescriptor | Array -} -type TwitterApp = TwitterMetadata & { - card: 'app' - app: TwitterAppDescriptor -} -type TwitterAppDescriptor = { - id: { - iphone?: string | number - ipad?: string | number - googleplay?: string - } - url?: { - iphone?: string | URL - ipad?: string | URL - googleplay?: string | URL - } - country?: string -} - -type TwitterImage = string | TwitterImageDescriptor | URL -type TwitterImageDescriptor = { - url: string | URL - secureUrl?: string | URL - alt?: string - type?: string - width?: string | number - height?: string | number -} -type TwitterPlayerDescriptor = { - playerUrl: string | URL - streamUrl: string | URL - width: number - height: number -} - -export type ResolvedTwitterMetadata = Omit & { - title: AbsoluteTemplateString | null -} diff --git a/packages/next/src/lib/metadata/ui.tsx b/packages/next/src/lib/metadata/ui.tsx deleted file mode 100644 index b79dfca145c17..0000000000000 --- a/packages/next/src/lib/metadata/ui.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react' - -import type { ResolvedMetadata } from './types/metadata-interface' -import { ResolvedBasicMetadata } from './generate/basic' -import { ResolvedAlternatesMetadata } from './generate/alternate' -import { ResolvedOpenGraphMetadata } from './generate/opengraph' -import { resolveMetadata } from './resolve-metadata' - -// Generate the actual React elements from the resolved metadata. -export async function Metadata({ metadata }: { metadata: any }) { - const resolved: ResolvedMetadata = await resolveMetadata(metadata) - return ( - <> - - - - - ) -} diff --git a/packages/next/src/server/app-render.tsx b/packages/next/src/server/app-render.tsx index d23e9d50603c7..80981fac3d2d5 100644 --- a/packages/next/src/server/app-render.tsx +++ b/packages/next/src/server/app-render.tsx @@ -45,8 +45,8 @@ import { RSC, } from '../client/components/app-router-headers' import type { StaticGenerationAsyncStorage } from '../client/components/static-generation-async-storage' +import { DefaultHead } from '../client/components/head' import { formatServerError } from '../lib/format-server-error' -import { Metadata } from '../lib/metadata/ui' import type { RequestAsyncStorage } from '../client/components/request-async-storage' import { runWithRequestAsyncStorage } from './run-with-request-async-storage' import { runWithStaticGenerationAsyncStorage } from './run-with-static-generation-async-storage' @@ -949,12 +949,6 @@ export async function renderToHTMLOrFlight( */ const loaderTree: LoaderTree = ComponentMod.tree - /** - * The metadata items array created in next-app-loader with all relevant information - * that we need to resolve the final metadata. - */ - const metadataItems = ComponentMod.metadata - stripInternalQueries(query) const LayoutRouter = @@ -1038,7 +1032,8 @@ export async function renderToHTMLOrFlight( async function resolveHead( [segment, parallelRoutes, { head }]: LoaderTree, - parentParams: { [key: string]: any } + parentParams: { [key: string]: any }, + isRootHead: boolean ): Promise { // Handle dynamic segment params. const segmentParam = getDynamicParamFromSegment(segment) @@ -1056,7 +1051,7 @@ export async function renderToHTMLOrFlight( parentParams for (const key in parallelRoutes) { const childTree = parallelRoutes[key] - const returnedHead = await resolveHead(childTree, currentParams) + const returnedHead = await resolveHead(childTree, currentParams, false) if (returnedHead) { return returnedHead } @@ -1065,6 +1060,8 @@ export async function renderToHTMLOrFlight( if (head) { const Head = await interopDefault(await head[0]()) return + } else if (isRootHead) { + return } return null @@ -1667,7 +1664,7 @@ export async function renderToHTMLOrFlight( return [actualSegment] } - const rscPayloadHead = await resolveHead(loaderTree, {}) + const rscPayloadHead = await resolveHead(loaderTree, {}, true) // 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 = [ @@ -1678,13 +1675,7 @@ export async function renderToHTMLOrFlight( parentParams: {}, flightRouterState: providedFlightRouterState, isFirst: true, - rscPayloadHead: ( - <> - {/* @ts-expect-error allow to use async server component */} - - {rscPayloadHead} - - ), + rscPayloadHead, injectedCSS: new Set(), rootLayoutIncluded: false, }) @@ -1751,7 +1742,7 @@ export async function renderToHTMLOrFlight( } : {} - const initialHead = await resolveHead(loaderTree, {}) + const initialHead = await resolveHead(loaderTree, {}, true) /** * A new React Component that renders the provided React Component @@ -1768,27 +1759,18 @@ export async function renderToHTMLOrFlight( injectedCSS: new Set(), rootLayoutIncluded: false, }) - const initialTree = createFlightRouterStateFromLoaderTree(loaderTree) return ( - <> - - {/* @ts-expect-error allow to use async server component */} - - {initialHead} - - } - globalErrorComponent={GlobalError} - > - - - + + + ) }, ComponentMod, diff --git a/test/e2e/app-dir/app/index.test.ts b/test/e2e/app-dir/app/index.test.ts index 5b18179a00392..3df9b4b1a939e 100644 --- a/test/e2e/app-dir/app/index.test.ts +++ b/test/e2e/app-dir/app/index.test.ts @@ -85,7 +85,7 @@ createNextDescribe( it('should pass props from getServerSideProps in root layout', async () => { const $ = await next.render$('/dashboard') - expect($('title').first().text()).toBe('hello world') + expect($('title').text()).toBe('hello world') }) it('should serve from pages', async () => { diff --git a/test/e2e/app-dir/metadata/app/alternate/page.js b/test/e2e/app-dir/metadata/app/alternate/page.js deleted file mode 100644 index 25777130e9a6f..0000000000000 --- a/test/e2e/app-dir/metadata/app/alternate/page.js +++ /dev/null @@ -1,19 +0,0 @@ -export default function Page() { - return

hello

-} - -export const metadata = { - alternates: { - canonical: 'https://example.com', - languages: { - 'en-US': 'https://example.com/en-US', - 'de-DE': 'https://example.com/de-DE', - }, - media: { - 'only screen and (max-width: 600px)': 'https://example.com/mobile', - }, - types: { - 'application/rss+xml': 'https://example.com/rss', - }, - }, -} diff --git a/test/e2e/app-dir/metadata/app/basic/page.js b/test/e2e/app-dir/metadata/app/basic/page.js deleted file mode 100644 index 224dd10988942..0000000000000 --- a/test/e2e/app-dir/metadata/app/basic/page.js +++ /dev/null @@ -1,25 +0,0 @@ -import Link from 'next/link' - -export default function Page() { - return ( -
- - to index - -
- ) -} - -export const metadata = { - generator: 'next.js', - applicationName: 'test', - referrer: 'origin-when-crossorigin', - keywords: ['next.js', 'react', 'javascript'], - authors: ['John Doe', 'Jane Doe'], - themeColor: 'cyan', - colorScheme: 'dark', - viewport: 'width=device-width, initial-scale=1, shrink-to-fit=no', - creator: 'shu', - publisher: 'vercel', - robots: 'index, follow', -} diff --git a/test/e2e/app-dir/metadata/app/layout.js b/test/e2e/app-dir/metadata/app/layout.js deleted file mode 100644 index e427799cce6f7..0000000000000 --- a/test/e2e/app-dir/metadata/app/layout.js +++ /dev/null @@ -1,13 +0,0 @@ -export default function Layout({ children }) { - return ( - - - {children} - - ) -} - -export const metadata = { - title: 'this is the layout title', - description: 'this is the layout description', -} diff --git a/test/e2e/app-dir/metadata/app/opengraph/article/page.js b/test/e2e/app-dir/metadata/app/opengraph/article/page.js deleted file mode 100644 index 4b92b111d6610..0000000000000 --- a/test/e2e/app-dir/metadata/app/opengraph/article/page.js +++ /dev/null @@ -1,13 +0,0 @@ -export default function Page() { - return

hello

-} - -export const metadata = { - openGraph: { - title: 'My custom title', - description: 'My custom description', - type: 'article', - publishedTime: '2023-01-01T00:00:00.000Z', - authors: ['author1', 'author2', 'author3'], - }, -} diff --git a/test/e2e/app-dir/metadata/app/opengraph/page.js b/test/e2e/app-dir/metadata/app/opengraph/page.js deleted file mode 100644 index 970a521bf382a..0000000000000 --- a/test/e2e/app-dir/metadata/app/opengraph/page.js +++ /dev/null @@ -1,27 +0,0 @@ -export default function Page() { - return

hello

-} - -export const metadata = { - openGraph: { - title: 'My custom title', - description: 'My custom description', - url: 'https://example.com', - siteName: 'My custom site name', - images: [ - { - url: 'https://example.com/image.png', - width: 800, - height: 600, - }, - { - url: 'https://example.com/image2.png', - width: 1800, - height: 1600, - alt: 'My custom alt', - }, - ], - locale: 'en-US', - type: 'website', - }, -} diff --git a/test/e2e/app-dir/metadata/app/page.js b/test/e2e/app-dir/metadata/app/page.js deleted file mode 100644 index 202d1179a5c86..0000000000000 --- a/test/e2e/app-dir/metadata/app/page.js +++ /dev/null @@ -1,23 +0,0 @@ -import Link from 'next/link' - -export default function Page() { - return ( - <> -

index page

- - - to /basic - -
- - - to /title - -
- - ) -} - -export const metadata = { - title: 'index page', -} diff --git a/test/e2e/app-dir/metadata/app/title-template/extra/inner/page.js b/test/e2e/app-dir/metadata/app/title-template/extra/inner/page.js deleted file mode 100644 index 426364f16fb38..0000000000000 --- a/test/e2e/app-dir/metadata/app/title-template/extra/inner/page.js +++ /dev/null @@ -1,7 +0,0 @@ -export default function Page() { - return

hello

-} - -export const metadata = { - title: 'Inner Page', -} diff --git a/test/e2e/app-dir/metadata/app/title-template/extra/layout.js b/test/e2e/app-dir/metadata/app/title-template/extra/layout.js deleted file mode 100644 index 93f67bdbf497e..0000000000000 --- a/test/e2e/app-dir/metadata/app/title-template/extra/layout.js +++ /dev/null @@ -1,9 +0,0 @@ -export default function Layout(props) { - return props.children -} - -export const metadata = { - title: { - template: '%s | Extra Layout', - }, -} diff --git a/test/e2e/app-dir/metadata/app/title-template/extra/page.js b/test/e2e/app-dir/metadata/app/title-template/extra/page.js deleted file mode 100644 index ea77ded22896d..0000000000000 --- a/test/e2e/app-dir/metadata/app/title-template/extra/page.js +++ /dev/null @@ -1,7 +0,0 @@ -export default function Page() { - return

hello

-} - -export const metadata = { - title: 'Extra Page', -} diff --git a/test/e2e/app-dir/metadata/app/title-template/layout.js b/test/e2e/app-dir/metadata/app/title-template/layout.js deleted file mode 100644 index adec3c2eb8874..0000000000000 --- a/test/e2e/app-dir/metadata/app/title-template/layout.js +++ /dev/null @@ -1,9 +0,0 @@ -export default function Layout(props) { - return props.children -} - -export const metadata = { - title: { - template: '%s | Layout', - }, -} diff --git a/test/e2e/app-dir/metadata/app/title-template/page.js b/test/e2e/app-dir/metadata/app/title-template/page.js deleted file mode 100644 index 2fbc7681df1ea..0000000000000 --- a/test/e2e/app-dir/metadata/app/title-template/page.js +++ /dev/null @@ -1,7 +0,0 @@ -export default function Page() { - return

hello

-} - -export const metadata = { - title: 'Page', -} diff --git a/test/e2e/app-dir/metadata/app/title/page.js b/test/e2e/app-dir/metadata/app/title/page.js deleted file mode 100644 index 9e63975b66874..0000000000000 --- a/test/e2e/app-dir/metadata/app/title/page.js +++ /dev/null @@ -1,15 +0,0 @@ -import Link from 'next/link' - -export default function Page() { - return ( -
- - to index - -
- ) -} - -export const metadata = { - title: 'this is the page title', -} diff --git a/test/e2e/app-dir/metadata/app/viewport/object/page.js b/test/e2e/app-dir/metadata/app/viewport/object/page.js deleted file mode 100644 index e82899ae607df..0000000000000 --- a/test/e2e/app-dir/metadata/app/viewport/object/page.js +++ /dev/null @@ -1,11 +0,0 @@ -export default function Page() { - return

viewport

-} - -export const metadata = { - viewport: { - width: 'device-width', - initialScale: 1, - maximumScale: 1, - }, -} diff --git a/test/e2e/app-dir/metadata/metadata.test.ts b/test/e2e/app-dir/metadata/metadata.test.ts deleted file mode 100644 index 1d805490533d8..0000000000000 --- a/test/e2e/app-dir/metadata/metadata.test.ts +++ /dev/null @@ -1,214 +0,0 @@ -import { createNextDescribe } from 'e2e-utils' - -createNextDescribe( - 'app dir - metadata', - { - files: __dirname, - }, - ({ next, isNextDeploy }) => { - describe('metadata', () => { - if (isNextDeploy) { - it('should skip for deploy currently', () => {}) - return - } - async function checkMeta( - browser, - name, - content, - property = 'property', - tag = 'meta', - field = 'content' - ) { - const values = await browser.eval( - `[...document.querySelectorAll('${tag}[${property}="${name}"]')].map((el) => el.${field})` - ) - if (Array.isArray(content)) { - expect(values).toEqual(content) - } else { - console.log('expect', values[0], 'toContain', content) - expect(values[0]).toContain(content) - } - } - - describe('basic', () => { - it('should support title and description', async () => { - const browser = await next.browser('/title') - expect(await browser.eval(`document.title`)).toBe( - 'this is the page title' - ) - await checkMeta( - browser, - 'description', - 'this is the layout description', - 'name' - ) - }) - - it('should support title template', async () => { - const browser = await next.browser('/title-template') - expect(await browser.eval(`document.title`)).toBe('Page | Layout') - }) - - it('should support stashed title in one layer of page and layout', async () => { - const browser = await next.browser('/title-template/extra') - expect(await browser.eval(`document.title`)).toBe( - 'Extra Page | Extra Layout' - ) - }) - - it('should support stashed title in two layers of page and layout', async () => { - const browser = await next.browser('/title-template/extra/inner') - expect(await browser.eval(`document.title`)).toBe( - 'Inner Page | Extra Layout' - ) - }) - - it('should support other basic tags', async () => { - const browser = await next.browser('/basic') - await checkMeta(browser, 'generator', 'next.js', 'name') - await checkMeta(browser, 'application-name', 'test', 'name') - await checkMeta( - browser, - 'referrer', - 'origin-when-crossorigin', - 'name' - ) - await checkMeta( - browser, - 'keywords', - 'next.js,react,javascript', - 'name' - ) - await checkMeta(browser, 'author', 'John Doe,Jane Doe', 'name') - await checkMeta(browser, 'theme-color', 'cyan', 'name') - await checkMeta(browser, 'color-scheme', 'dark', 'name') - await checkMeta( - browser, - 'viewport', - 'width=device-width, initial-scale=1, shrink-to-fit=no', - 'name' - ) - await checkMeta(browser, 'creator', 'shu', 'name') - await checkMeta(browser, 'publisher', 'vercel', 'name') - await checkMeta(browser, 'robots', 'index, follow', 'name') - }) - - it('should support object viewport', async () => { - const browser = await next.browser('/viewport/object') - await checkMeta( - browser, - 'viewport', - 'width=device-width, initial-scale=1, maximum-scale=1', - 'name' - ) - }) - - it('should support alternate tags', async () => { - const browser = await next.browser('/alternate') - await checkMeta( - browser, - 'canonical', - 'https://example.com', - 'rel', - 'link', - 'href' - ) - await checkMeta( - browser, - 'en-US', - 'https://example.com/en-US', - 'hreflang', - 'link', - 'href' - ) - await checkMeta( - browser, - 'de-DE', - 'https://example.com/de-DE', - 'hreflang', - 'link', - 'href' - ) - await checkMeta( - browser, - 'only screen and (max-width: 600px)', - 'https://example.com/mobile', - 'media', - 'link', - 'href' - ) - await checkMeta( - browser, - 'application/rss+xml', - 'https://example.com/rss', - 'type', - 'link', - 'href' - ) - }) - - it('should apply metadata when navigating client-side', async () => { - const browser = await next.browser('/') - - const getTitle = () => browser.elementByCss('title').text() - - expect(await getTitle()).toBe('index page') - await browser - .elementByCss('#to-basic') - .click() - .waitForElementByCss('#basic', 2000) - - await checkMeta( - browser, - 'referrer', - 'origin-when-crossorigin', - 'name' - ) - await browser.back().waitForElementByCss('#index', 2000) - expect(await getTitle()).toBe('index page') - await browser - .elementByCss('#to-title') - .click() - .waitForElementByCss('#title', 2000) - expect(await getTitle()).toBe('this is the page title') - }) - }) - - describe('opengraph', () => { - it('should support opengraph tags', async () => { - const browser = await next.browser('/opengraph') - await checkMeta(browser, 'og:title', 'My custom title') - await checkMeta(browser, 'og:description', 'My custom description') - await checkMeta(browser, 'og:url', 'https://example.com') - await checkMeta(browser, 'og:site_name', 'My custom site name') - await checkMeta(browser, 'og:locale', 'en-US') - await checkMeta(browser, 'og:type', 'website') - await checkMeta(browser, 'og:image:url', [ - 'https://example.com/image.png', - 'https://example.com/image2.png', - ]) - await checkMeta(browser, 'og:image:width', ['800', '1800']) - await checkMeta(browser, 'og:image:height', ['600', '1600']) - await checkMeta(browser, 'og:image:alt', 'My custom alt') - }) - - it('should support opengraph with article type', async () => { - const browser = await next.browser('/opengraph/article') - await checkMeta(browser, 'og:title', 'My custom title') - await checkMeta(browser, 'og:description', 'My custom description') - await checkMeta(browser, 'og:type', 'article') - await checkMeta( - browser, - 'article:published_time', - '2023-01-01T00:00:00.000Z' - ) - await checkMeta(browser, 'article:author', [ - 'author1', - 'author2', - 'author3', - ]) - }) - }) - }) - } -) diff --git a/test/e2e/app-dir/metadata/next.config.js b/test/e2e/app-dir/metadata/next.config.js deleted file mode 100644 index 8e2a6c3691744..0000000000000 --- a/test/e2e/app-dir/metadata/next.config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - experimental: { appDir: true }, -} diff --git a/test/e2e/app-dir/rsc-basic/rsc-basic.test.ts b/test/e2e/app-dir/rsc-basic/rsc-basic.test.ts index 314e932e7bef3..1c9302892594e 100644 --- a/test/e2e/app-dir/rsc-basic/rsc-basic.test.ts +++ b/test/e2e/app-dir/rsc-basic/rsc-basic.test.ts @@ -103,11 +103,9 @@ describe('app dir - rsc basics', () => { // should have only 1 DOCTYPE expect(homeHTML).toMatch(/^') expect(homeHTML).toContain( - '' + '' ) - expect(homeHTML).toContain('component:index.server') expect(homeHTML).toContain('header:test-util')