Skip to content

Commit

Permalink
Static generate dynamic sitemaps (#49114)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
huozhi authored May 3, 2023
1 parent ffa23d2 commit c09b0a0
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 9 deletions.
13 changes: 13 additions & 0 deletions packages/next/src/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}

Expand Down
1 change: 1 addition & 0 deletions packages/next/src/build/webpack/loaders/next-app-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const cacheHeader = {
}

type MetadataRouteLoaderOptions = {
page: string
pageExtensions: string[]
}

Expand Down Expand Up @@ -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'
Expand Down Expand Up @@ -172,15 +191,18 @@ 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.
// TODO-METADATA: improve the cache control strategy
const nextMetadataRouterLoader: webpack.LoaderDefinitionFunction<MetadataRouteLoaderOptions> =
function () {
const { resourcePath } = this
const { pageExtensions } = this.getOptions()
const { pageExtensions, page } = this.getOptions()

const { name: fileBaseName, ext } = getFilenameAndExtension(resourcePath)
const isDynamic = pageExtensions.includes(ext)
Expand All @@ -190,7 +212,7 @@ const nextMetadataRouterLoader: webpack.LoaderDefinitionFunction<MetadataRouteLo
if (fileBaseName === 'robots' || fileBaseName === 'manifest') {
code = getDynamicTextRouteCode(resourcePath)
} else if (fileBaseName === 'sitemap') {
code = getDynamicSiteMapRouteCode(resourcePath)
code = getDynamicSiteMapRouteCode(resourcePath, page)
} else {
code = getDynamicImageRouteCode(resourcePath)
}
Expand Down
3 changes: 3 additions & 0 deletions test/e2e/app-dir/metadata-dynamic-routes/app/gsp/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Page() {
return <div>gsp</div>
}
20 changes: 16 additions & 4 deletions test/e2e/app-dir/metadata-dynamic-routes/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}

Expand Down Expand Up @@ -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(
Expand Down

0 comments on commit c09b0a0

Please sign in to comment.