diff --git a/packages/next/src/build/webpack/loaders/metadata/discover.ts b/packages/next/src/build/webpack/loaders/metadata/discover.ts index 3081ded78e92c6..91e8fbb5ce61ca 100644 --- a/packages/next/src/build/webpack/loaders/metadata/discover.ts +++ b/packages/next/src/build/webpack/loaders/metadata/discover.ts @@ -31,13 +31,13 @@ export const STATIC_METADATA_IMAGES = { filename: 'twitter-image', extensions: ['jpg', 'jpeg', 'png', 'gif'], }, -} as const +} // Produce all compositions with filename (icon, apple-icon, etc.) with extensions (png, jpg, etc.) async function enumMetadataFiles( dir: string, filename: string, - extensions: readonly string[], + extensions: string[], { resolvePath, loaderContext, diff --git a/packages/next/src/build/webpack/loaders/metadata/resolve-route-data.test.ts b/packages/next/src/build/webpack/loaders/metadata/resolve-route-data.test.ts index 38261e75ee21c4..684d94ddc697d9 100644 --- a/packages/next/src/build/webpack/loaders/metadata/resolve-route-data.test.ts +++ b/packages/next/src/build/webpack/loaders/metadata/resolve-route-data.test.ts @@ -1,4 +1,4 @@ -import type { Robots } from '../../../../lib/metadata/types/metadata-interface' +import type { RobotsFile } from '../../../../lib/metadata/types/metadata-interface' import { resolveRobots, resolveSitemap } from './resolve-route-data' describe('resolveRouteData', () => { @@ -30,7 +30,7 @@ describe('resolveRouteData', () => { }) it('should error with ts when specify both wildcard userAgent and specific userAgent', () => { - const data1: Robots = { + const data1: RobotsFile = { rules: [ // @ts-expect-error userAgent is required for Array { @@ -43,14 +43,15 @@ describe('resolveRouteData', () => { ], } - const data2: Robots = { + const data2: RobotsFile = { rules: { - // Can skip userAgent for single Robots + // @ts-expect-error When apply only 1 rule, only '*' or undefined is allowed + userAgent: 'Somebot', allow: '/', }, } - const data3: Robots = { + const data3: RobotsFile = { rules: { allow: '/' }, } diff --git a/packages/next/src/build/webpack/loaders/metadata/resolve-route-data.ts b/packages/next/src/build/webpack/loaders/metadata/resolve-route-data.ts index b2f1b1edaaba63..fbb39f7d7fabf5 100644 --- a/packages/next/src/build/webpack/loaders/metadata/resolve-route-data.ts +++ b/packages/next/src/build/webpack/loaders/metadata/resolve-route-data.ts @@ -1,12 +1,11 @@ import type { - Robots, - Sitemap, + RobotsFile, + SitemapFile, } from '../../../../lib/metadata/types/metadata-interface' -import type { Manifest } from '../../../../lib/metadata/types/manifest-types' import { resolveAsArrayOrUndefined } from '../../../../lib/metadata/generate/utils' // convert robots data to txt string -export function resolveRobots(data: Robots): string { +export function resolveRobots(data: RobotsFile): string { let content = '' const rules = Array.isArray(data.rules) ? data.rules : [data.rules] for (const rule of rules) { @@ -41,7 +40,7 @@ export function resolveRobots(data: Robots): string { // TODO-METADATA: support multi sitemap files // convert sitemap data to xml string -export function resolveSitemap(data: Sitemap): string { +export function resolveSitemap(data: SitemapFile): string { let content = '' content += '\n' content += '\n' @@ -61,22 +60,15 @@ export function resolveSitemap(data: Sitemap): string { return content } -export function resolveManifest(data: Manifest): string { - return JSON.stringify(data) -} - export function resolveRouteData( - data: Robots | Sitemap | Manifest, - fileType: 'robots' | 'sitemap' | 'manifest' + data: RobotsFile | SitemapFile, + fileType: 'robots' | 'sitemap' ): string { if (fileType === 'robots') { - return resolveRobots(data as Robots) + return resolveRobots(data as RobotsFile) } if (fileType === 'sitemap') { - return resolveSitemap(data as Sitemap) - } - if (fileType === 'manifest') { - return resolveManifest(data as Manifest) + return resolveSitemap(data as SitemapFile) } return '' } diff --git a/packages/next/src/build/webpack/loaders/next-metadata-route-loader.ts b/packages/next/src/build/webpack/loaders/next-metadata-route-loader.ts index 6b3797e8e2b13b..d465ec974508c8 100644 --- a/packages/next/src/build/webpack/loaders/next-metadata-route-loader.ts +++ b/packages/next/src/build/webpack/loaders/next-metadata-route-loader.ts @@ -20,7 +20,6 @@ function getContentType(resourcePath: string) { if (name === 'favicon' && ext === 'ico') return 'image/x-icon' if (name === 'sitemap') return 'application/xml' if (name === 'robots') return 'text/plain' - if (name === 'manifest') return 'application/manifest+json' if (ext === 'png' || ext === 'jpeg' || ext === 'ico' || ext === 'svg') { return imageExtMimeTypeMap[ext] diff --git a/packages/next/src/lib/metadata/get-metadata-route.ts b/packages/next/src/lib/metadata/get-metadata-route.ts index 10b56b893d60d5..b7644e32b94b2f 100644 --- a/packages/next/src/lib/metadata/get-metadata-route.ts +++ b/packages/next/src/lib/metadata/get-metadata-route.ts @@ -20,9 +20,6 @@ export function normalizeMetadataRoute(page: string) { if (route === '/robots') { route += '.txt' } - if (route === '/manifest') { - route += '.webmanifest' - } route = `${route}/route` } return route diff --git a/packages/next/src/lib/metadata/is-metadata-route.ts b/packages/next/src/lib/metadata/is-metadata-route.ts index d23578befa0afa..e58c978118a478 100644 --- a/packages/next/src/lib/metadata/is-metadata-route.ts +++ b/packages/next/src/lib/metadata/is-metadata-route.ts @@ -4,15 +4,15 @@ import { STATIC_METADATA_IMAGES } from '../../build/webpack/loaders/metadata/dis // TODO-METADATA: support more metadata routes with more extensions const defaultExtensions = ['js', 'jsx', 'ts', 'tsx'] -const getExtensionRegexString = (extensions: readonly string[]) => +const getExtensionRegexString = (extensions: string[]) => `(?:${extensions.join('|')})` // When you only pass the file extension as `[]`, it will only match the static convention files -// e.g. /robots.txt, /sitemap.xml, /favicon.ico, /manifest.json +// e.g. /robots.txt, /sitemap.xml, /favicon.ico // When you pass the file extension as `['js', 'jsx', 'ts', 'tsx']`, it will also match the dynamic convention files -// e.g. /robots.js, /sitemap.tsx, /favicon.jsx, /manifest.ts +// e.g. /robots.js, /sitemap.tsx, /favicon.jsx // When `withExtension` is false, it will match the static convention files without the extension, by default it's true -// e.g. /robots, /sitemap, /favicon, /manifest, use to match dynamic API routes like app/robots.ts +// e.g. /robots, /sitemap, /favicon, use to match dynamic API routes like app/robots.ts export function isMetadataRouteFile( appDirRelativePath: string, pageExtensions: string[], @@ -33,15 +33,6 @@ export function isMetadataRouteFile( : '' }` ), - new RegExp( - `^[\\\\/]manifest${ - withExtension - ? `\\.${getExtensionRegexString( - pageExtensions.concat('webmanifest', 'json') - )}` - : '' - }` - ), new RegExp(`^[\\\\/]favicon\\.ico$`), // TODO-METADATA: add dynamic routes for metadata images new RegExp( 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 cf683abfae8f5b..00000000000000 --- a/packages/next/src/lib/metadata/types/manifest-types.ts +++ /dev/null @@ -1,86 +0,0 @@ -export type Manifest = { - background_color?: string - categories?: string[] - description?: string - display?: 'fullscreen' | 'standalone' | 'minimal-ui' | 'browser' - display_override?: string[] - icons?: { - src: string - type?: string - sizes?: string - purpose?: 'any' | 'maskable' | 'monochrome' | 'badge' - }[] - id?: string - launch_handler?: { - platform?: 'windows' | 'macos' | 'linux' - url?: string - } - name?: string - orientation?: - | 'any' - | 'natural' - | 'landscape' - | 'portrait' - | 'portrait-primary' - | 'portrait-secondary' - | 'landscape-primary' - | 'landscape-secondary' - prefer_related_applications?: boolean - protocol_handlers?: { - protocol: string - url: string - title?: string - }[] - related_applications?: { - platform: string - url: string - id?: string - }[] - scope?: string - screenshots?: { - src: string - type?: string - sizes?: string - }[] - serviceworker?: { - src?: string - scope?: string - type?: string - update_via_cache?: 'import' | 'none' | 'all' - } - share_target?: { - action?: string - method?: 'get' | 'post' - enctype?: - | 'application/x-www-form-urlencoded' - | 'multipart/form-data' - | 'text/plain' - params?: { - name: string - value: string - required?: boolean - }[] - url?: string - title?: string - text?: string - files?: { - accept?: string[] - name?: string - }[] - } - short_name?: string - shortcuts?: { - name: string - short_name?: string - description?: string - url: string - icons?: { - src: string - type?: string - sizes?: string - purpose?: 'any' | 'maskable' | 'monochrome' | 'badge' - }[] - }[] - start_url?: string - theme_color?: string -} diff --git a/packages/next/src/lib/metadata/types/metadata-interface.ts b/packages/next/src/lib/metadata/types/metadata-interface.ts index bf4dd99bf27880..10deb233e5b5dc 100644 --- a/packages/next/src/lib/metadata/types/metadata-interface.ts +++ b/packages/next/src/lib/metadata/types/metadata-interface.ts @@ -538,7 +538,7 @@ type RobotsFile = { // Apply rules for all rules: | { - userAgent?: string | string[] + userAgent?: undefined | '*' allow?: string | string[] disallow?: string | string[] crawlDelay?: number @@ -554,10 +554,10 @@ type RobotsFile = { host?: string } -type Sitemap = Array<{ +type SitemapFile = Array<{ url: string lastModified?: string | Date }> export type ResolvingMetadata = Promise -export { Metadata, ResolvedMetadata, RobotsFile as Robots, Sitemap } +export { Metadata, ResolvedMetadata, RobotsFile, SitemapFile } diff --git a/packages/next/src/server/lib/find-page-file.ts b/packages/next/src/server/lib/find-page-file.ts index 6cf514a8342741..61f25b1d58be83 100644 --- a/packages/next/src/server/lib/find-page-file.ts +++ b/packages/next/src/server/lib/find-page-file.ts @@ -97,7 +97,6 @@ export function createValidFileMatcher( * /robots.txt| * /sitemap.xml| * /favicon.ico - * /manifest.json| * /icon.png|jpg| * /apple-touch-icon.png|jpg| * diff --git a/packages/next/types/index.d.ts b/packages/next/types/index.d.ts index 711cbacf434d78..876d1a152d9958 100644 --- a/packages/next/types/index.d.ts +++ b/packages/next/types/index.d.ts @@ -28,8 +28,11 @@ export type ServerRuntime = 'nodejs' | 'experimental-edge' | 'edge' | undefined // @ts-ignore This path is generated at build time and conflicts otherwise export { NextConfig } from '../dist/server/config' -// @ts-ignore This path is generated at build time and conflicts otherwise -export type { Metadata } from '../dist/lib/metadata/types/metadata-interface' +export type { + Metadata, + RobotsFile, + SitemapFile, // @ts-ignore This path is generated at build time and conflicts otherwise +} from '../dist/lib/metadata/types/metadata-interface' // Extend the React types with missing properties declare module 'react' { diff --git a/test/e2e/app-dir/metadata-dynamic-routes/app/manifest.ts b/test/e2e/app-dir/metadata-dynamic-routes/app/manifest.ts deleted file mode 100644 index 736cffed86b601..00000000000000 --- a/test/e2e/app-dir/metadata-dynamic-routes/app/manifest.ts +++ /dev/null @@ -1,18 +0,0 @@ -export default function manifest() { - return { - name: 'Next.js App', - short_name: 'Next.js App', - description: 'Next.js App', - start_url: '/', - display: 'standalone', - background_color: '#fff', - theme_color: '#fff', - icons: [ - { - src: '/favicon.ico', - sizes: 'any', - type: 'image/x-icon', - }, - ], - } -} diff --git a/test/e2e/app-dir/metadata-dynamic-routes/app/robots.ts b/test/e2e/app-dir/metadata-dynamic-routes/app/robots.ts index 749881098a1eab..5a2bb37510ed02 100644 --- a/test/e2e/app-dir/metadata-dynamic-routes/app/robots.ts +++ b/test/e2e/app-dir/metadata-dynamic-routes/app/robots.ts @@ -1,4 +1,6 @@ -export default function robots() { +import type { RobotsFile } from 'next' + +export default function robots(): RobotsFile { return { rules: [ { diff --git a/test/e2e/app-dir/metadata-dynamic-routes/app/sitemap.ts b/test/e2e/app-dir/metadata-dynamic-routes/app/sitemap.ts index 7b1426aa7ea45c..76a55cc17e47cc 100644 --- a/test/e2e/app-dir/metadata-dynamic-routes/app/sitemap.ts +++ b/test/e2e/app-dir/metadata-dynamic-routes/app/sitemap.ts @@ -1,4 +1,6 @@ -export default function sitemap() { +import type { SitemapFile } from 'next' + +export default function sitemap(): SitemapFile { return [ { url: 'https://example.com', diff --git a/test/e2e/app-dir/metadata-dynamic-routes/index.test.ts b/test/e2e/app-dir/metadata-dynamic-routes/index.test.ts index 4b49a9e9e84670..43d9844c22d495 100644 --- a/test/e2e/app-dir/metadata-dynamic-routes/index.test.ts +++ b/test/e2e/app-dir/metadata-dynamic-routes/index.test.ts @@ -56,35 +56,6 @@ createNextDescribe( " `) }) - - it('should handle manifest.[ext] dynamic routes', async () => { - const res = await next.fetch('/manifest.webmanifest') - const json = await res.json() - - expect(res.headers.get('content-type')).toBe( - 'application/manifest+json' - ) - expect(res.headers.get('cache-control')).toBe( - 'public, max-age=0, must-revalidate' - ) - - expect(json).toMatchObject({ - name: 'Next.js App', - short_name: 'Next.js App', - description: 'Next.js App', - start_url: '/', - display: 'standalone', - background_color: '#fff', - theme_color: '#fff', - icons: [ - { - src: '/favicon.ico', - sizes: 'any', - type: 'image/x-icon', - }, - ], - }) - }) }) } ) diff --git a/test/e2e/app-dir/metadata/app/basic/page.tsx b/test/e2e/app-dir/metadata/app/basic/page.tsx index 82f9f04e593a84..b9366e712bea97 100644 --- a/test/e2e/app-dir/metadata/app/basic/page.tsx +++ b/test/e2e/app-dir/metadata/app/basic/page.tsx @@ -25,7 +25,7 @@ export const metadata: Metadata = { authors: [{ name: 'huozhi' }, { name: 'tree', url: 'https://tree.com' }], themeColor: { color: 'cyan', media: '(prefers-color-scheme: dark)' }, colorScheme: 'dark', - manifest: 'https://www.google.com/manifest', + manifest: 'https://github.com/manifest.json', viewport: { width: 'device-width', initialScale: 1, diff --git a/test/e2e/app-dir/metadata/app/manifest.webmanifest b/test/e2e/app-dir/metadata/app/manifest.webmanifest deleted file mode 100644 index f3e287945395b0..00000000000000 --- a/test/e2e/app-dir/metadata/app/manifest.webmanifest +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "Next.js Static Manifest", - "short_name": "Next.js App", - "description": "Next.js App", - "start_url": "/", - "display": "standalone", - "background_color": "#fff", - "theme_color": "#fff" -} diff --git a/test/e2e/app-dir/metadata/metadata.test.ts b/test/e2e/app-dir/metadata/metadata.test.ts index 473282c78df896..abff83ac1434e0 100644 --- a/test/e2e/app-dir/metadata/metadata.test.ts +++ b/test/e2e/app-dir/metadata/metadata.test.ts @@ -210,7 +210,7 @@ createNextDescribe( }) await matchMultiDom('link', 'rel', 'href', { - manifest: 'https://www.google.com/manifest', + manifest: 'https://github.com/manifest.json', author: 'https://tree.com', preconnect: '/preconnect-url', preload: '/preload-url', @@ -687,23 +687,6 @@ createNextDescribe( expect(invalidSitemapResponse.status).toBe(404) }) - it('should support static manifest.webmanifest', async () => { - const res = await next.fetch('/manifest.webmanifest') - expect(res.headers.get('content-type')).toBe( - 'application/manifest+json' - ) - const manifest = await res.json() - expect(manifest).toMatchObject({ - name: 'Next.js Static Manifest', - short_name: 'Next.js App', - description: 'Next.js App', - start_url: '/', - display: 'standalone', - background_color: '#fff', - theme_color: '#fff', - }) - }) - if (isNextStart) { it('should build favicon.ico as a custom route', async () => { const appPathsManifest = JSON.parse(