Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Static generate dynamic sitemaps #49114

Merged
merged 2 commits into from
May 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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