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 761170cde069a..bfddc65f066b2 100644 --- a/packages/next/src/build/webpack/loaders/next-app-loader.ts +++ b/packages/next/src/build/webpack/loaders/next-app-loader.ts @@ -124,8 +124,9 @@ async function createAppRouteCode({ resolvedPagePath = `next-metadata-route-loader?${stringify({ page, + filePath: resolvedPagePath, isDynamic: isDynamic ? '1' : '0', - })}!${resolvedPagePath}${`?${WEBPACK_RESOURCE_QUERIES.metadataRoute}`}` + })}!?${WEBPACK_RESOURCE_QUERIES.metadataRoute}` } const pathname = new AppPathnameNormalizer().normalize(page) 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 49807f149cc6c..b9935bb576954 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 @@ -22,6 +22,7 @@ const cacheHeader = { type MetadataRouteLoaderOptions = { page: string + filePath: string isDynamic: '1' | '0' } @@ -46,7 +47,6 @@ function getContentType(resourcePath: string) { return 'text/plain' } -// Strip metadata resource query string from `import.meta.url` to make sure the fs.readFileSync get the right path. async function getStaticAssetRouteCode( resourcePath: string, fileBaseName: string @@ -58,6 +58,7 @@ async function getStaticAssetRouteCode( ? cacheHeader.none : cacheHeader.longCache const code = `\ +/* static asset route */ import { NextResponse } from 'next/server' const contentType = ${JSON.stringify(getContentType(resourcePath))} @@ -82,6 +83,7 @@ export const dynamic = 'force-static' function getDynamicTextRouteCode(resourcePath: string) { return `\ +/* dynamic asset route */ import { NextResponse } from 'next/server' import handler from ${JSON.stringify(resourcePath)} import { resolveRouteData } from 'next/dist/build/webpack/loaders/metadata/resolve-route-data' @@ -108,6 +110,7 @@ export async function GET() { // /[id]/route.js function getDynamicImageRouteCode(resourcePath: string) { return `\ +/* dynamic image route */ import { NextResponse } from 'next/server' import * as userland from ${JSON.stringify(resourcePath)} @@ -159,6 +162,7 @@ async function getDynamicSiteMapRouteCode( page.includes('[__metadata_id__]') ) { staticGenerationCode = `\ +/* dynamic sitemap route */ export async function generateStaticParams() { const sitemaps = generateSitemaps ? await generateSitemaps() : [] const params = [] @@ -232,26 +236,25 @@ ${staticGenerationCode} ` return code } -// `import.meta.url` is the resource name of the current module. + // When it's static route, it could be favicon.ico, sitemap.xml, robots.txt etc. // TODO-METADATA: improve the cache control strategy const nextMetadataRouterLoader: webpack.LoaderDefinitionFunction = async function () { - const { resourcePath } = this - const { page, isDynamic } = this.getOptions() - const { name: fileBaseName } = getFilenameAndExtension(resourcePath) + const { page, isDynamic, filePath } = this.getOptions() + const { name: fileBaseName } = getFilenameAndExtension(filePath) let code = '' if (isDynamic === '1') { if (fileBaseName === 'robots' || fileBaseName === 'manifest') { - code = getDynamicTextRouteCode(resourcePath) + code = getDynamicTextRouteCode(filePath) } else if (fileBaseName === 'sitemap') { - code = await getDynamicSiteMapRouteCode(resourcePath, page, this) + code = await getDynamicSiteMapRouteCode(filePath, page, this) } else { - code = getDynamicImageRouteCode(resourcePath) + code = getDynamicImageRouteCode(filePath) } } else { - code = await getStaticAssetRouteCode(resourcePath, fileBaseName) + code = await getStaticAssetRouteCode(filePath, fileBaseName) } return code diff --git a/test/e2e/app-dir/metadata-json-manifest/app/layout.js b/test/e2e/app-dir/metadata-json-manifest/app/layout.js new file mode 100644 index 0000000000000..8525f5f8c0b2a --- /dev/null +++ b/test/e2e/app-dir/metadata-json-manifest/app/layout.js @@ -0,0 +1,12 @@ +export const metadata = { + title: 'Next.js', + description: 'Generated by Next.js', +} + +export default function RootLayout({ children }) { + return ( + + {children} + + ) +} diff --git a/test/e2e/app-dir/metadata-json-manifest/app/manifest.json b/test/e2e/app-dir/metadata-json-manifest/app/manifest.json new file mode 100644 index 0000000000000..4e09c9827989b --- /dev/null +++ b/test/e2e/app-dir/metadata-json-manifest/app/manifest.json @@ -0,0 +1,6 @@ +{ + "name": "My Next.js Application", + "short_name": "Next.js App", + "description": "An application built with Next.js", + "start_url": "/" +} diff --git a/test/e2e/app-dir/metadata-json-manifest/app/page.js b/test/e2e/app-dir/metadata-json-manifest/app/page.js new file mode 100644 index 0000000000000..cbce1149e8257 --- /dev/null +++ b/test/e2e/app-dir/metadata-json-manifest/app/page.js @@ -0,0 +1,3 @@ +export default function page() { + return 'page.js' +} diff --git a/test/e2e/app-dir/metadata-json-manifest/index.test.ts b/test/e2e/app-dir/metadata-json-manifest/index.test.ts new file mode 100644 index 0000000000000..9f58610e7346f --- /dev/null +++ b/test/e2e/app-dir/metadata-json-manifest/index.test.ts @@ -0,0 +1,22 @@ +import { createNextDescribe } from 'e2e-utils' + +createNextDescribe( + 'app-dir metadata-json-manifest', + { + files: __dirname, + skipDeployment: true, + }, + ({ next }) => { + it('should support metadata.json manifest', async () => { + const response = await next.fetch('/manifest.json') + expect(response.status).toBe(200) + const json = await response.json() + expect(json).toEqual({ + name: 'My Next.js Application', + short_name: 'Next.js App', + description: 'An application built with Next.js', + start_url: '/', + }) + }) + } +)