From c09b0a0890b6562c800b3cf7e2c75e6fb14c240c Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Wed, 3 May 2023 12:11:57 +0200 Subject: [PATCH] Static generate dynamic sitemaps (#49114) Follow up of #48867 - Statically optimize dynamic generated sitemap routes - Previously the generated sitemap urls looks bit off (`/route/sitemap.xml/[id]`), we polish it into `/route/sitemap/[id].xml` in this PR --- packages/next/src/build/index.ts | 13 ++++++++ .../build/webpack/loaders/next-app-loader.ts | 1 + .../loaders/next-metadata-route-loader.ts | 32 ++++++++++++++++--- .../metadata-dynamic-routes/app/gsp/page.tsx | 3 ++ .../dynamic/[size] => gsp}/sitemap.ts | 0 .../metadata-dynamic-routes/index.test.ts | 20 +++++++++--- 6 files changed, 60 insertions(+), 9 deletions(-) create mode 100644 test/e2e/app-dir/metadata-dynamic-routes/app/gsp/page.tsx rename test/e2e/app-dir/metadata-dynamic-routes/app/{(group)/dynamic/[size] => gsp}/sitemap.ts (100%) diff --git a/packages/next/src/build/index.ts b/packages/next/src/build/index.ts index d8740c19601f5..2bef4c14d6258 100644 --- a/packages/next/src/build/index.ts +++ b/packages/next/src/build/index.ts @@ -483,6 +483,19 @@ export default async function build( mappedAppPages[pageKey.replace('[[...__metadata_id__]]/', '')] = pagePath } + + if ( + pageKey.includes('sitemap.xml/[[...__metadata_id__]]') && + isDynamic + ) { + delete mappedAppPages[pageKey] + mappedAppPages[ + pageKey.replace( + 'sitemap.xml/[[...__metadata_id__]]', + 'sitemap/[__metadata_id__]' + ) + ] = pagePath + } } } 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 70600b5b286fb..7d2e698e538c5 100644 --- a/packages/next/src/build/webpack/loaders/next-app-loader.ts +++ b/packages/next/src/build/webpack/loaders/next-app-loader.ts @@ -99,6 +99,7 @@ async function createAppRouteCode({ const filename = path.parse(resolvedPagePath).name if (isMetadataRoute(name) && filename !== 'route') { resolvedPagePath = `next-metadata-route-loader?${stringify({ + page, pageExtensions, })}!${resolvedPagePath + METADATA_RESOURCE_QUERY}` } 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 c2ac2e2a9af2a..65af5e119b734 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 @@ -10,6 +10,7 @@ const cacheHeader = { } type MetadataRouteLoaderOptions = { + page: string pageExtensions: string[] } @@ -127,9 +128,27 @@ export async function GET(_, ctx) { ` } -function getDynamicSiteMapRouteCode(resourcePath: string) { - // generateSitemaps - return `\ +function getDynamicSiteMapRouteCode(resourcePath: string, page: string) { + let staticGenerationCode = '' + + if ( + process.env.NODE_ENV === 'production' && + page.includes('[__metadata_id__]') + ) { + staticGenerationCode = `\ +export async function generateStaticParams() { + const sitemaps = await generateSitemaps() + const params = [] + + for (const item of sitemaps) { + params.push({ __metadata_id__: item.id.toString() + '.xml' }) + } + return params +} + ` + } + + const code = `\ import { NextResponse } from 'next/server' import * as _sitemapModule from ${JSON.stringify(resourcePath)} import { resolveRouteData } from 'next/dist/build/webpack/loaders/metadata/resolve-route-data' @@ -172,7 +191,10 @@ export async function GET(_, ctx) { }, }) } + +${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. @@ -180,7 +202,7 @@ export async function GET(_, ctx) { const nextMetadataRouterLoader: webpack.LoaderDefinitionFunction = function () { const { resourcePath } = this - const { pageExtensions } = this.getOptions() + const { pageExtensions, page } = this.getOptions() const { name: fileBaseName, ext } = getFilenameAndExtension(resourcePath) const isDynamic = pageExtensions.includes(ext) @@ -190,7 +212,7 @@ const nextMetadataRouterLoader: webpack.LoaderDefinitionFunctiongsp +} diff --git a/test/e2e/app-dir/metadata-dynamic-routes/app/(group)/dynamic/[size]/sitemap.ts b/test/e2e/app-dir/metadata-dynamic-routes/app/gsp/sitemap.ts similarity index 100% rename from test/e2e/app-dir/metadata-dynamic-routes/app/(group)/dynamic/[size]/sitemap.ts rename to test/e2e/app-dir/metadata-dynamic-routes/app/gsp/sitemap.ts 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 aabcd057e0af3..71224c59c4f2f 100644 --- a/test/e2e/app-dir/metadata-dynamic-routes/index.test.ts +++ b/test/e2e/app-dir/metadata-dynamic-routes/index.test.ts @@ -159,10 +159,12 @@ createNextDescribe( }) it('should support generate multi sitemaps with generateSitemaps', async () => { - const ids = [0, 1, 2, 3] + const ids = [0, 1, 2] function fetchSitemap(id) { return next - .fetch(`/dynamic/small/sitemap.xml/${id}`) + .fetch( + isNextDev ? `/gsp/sitemap.xml/${id}` : `/gsp/sitemap/${id}.xml` + ) .then((res) => res.text()) } @@ -434,13 +436,23 @@ createNextDescribe( '/sitemap.xml/route': 'app/sitemap.xml/route.js', // dynamic - '/(group)/dynamic/[size]/sitemap.xml/[[...__metadata_id__]]/route': - 'app/(group)/dynamic/[size]/sitemap.xml/[[...__metadata_id__]]/route.js', + '/gsp/sitemap/[__metadata_id__]/route': + 'app/gsp/sitemap/[__metadata_id__]/route.js', '/(group)/dynamic/[size]/apple-icon-48jo90/[[...__metadata_id__]]/route': 'app/(group)/dynamic/[size]/apple-icon-48jo90/[[...__metadata_id__]]/route.js', }) }) + it('should generate static paths of dynamic sitemap in production', async () => { + const sitemapPaths = [0, 1, 2].map( + (id) => `.next/server/app/gsp/sitemap/${id}.xml.meta` + ) + const promises = sitemapPaths.map(async (filePath) => { + expect(await next.hasFile(filePath)).toBe(true) + }) + await Promise.all(promises) + }) + it('should include default og font files in file trace', async () => { const fileTrace = JSON.parse( await next.readFile(