diff --git a/src/module.ts b/src/module.ts
index 4aa7e6e9..ce0e3a09 100644
--- a/src/module.ts
+++ b/src/module.ts
@@ -319,8 +319,10 @@ declare module 'vue-router' {
nuxt.options.nitro.routeRules['/sitemap.xml'] = { redirect: '/sitemap_index.xml' }
nuxt.options.nitro.routeRules['/sitemap_index.xml'] = routeRules
if (typeof config.sitemaps === 'object') {
- for (const k in config.sitemaps)
- nuxt.options.nitro.routeRules[`/${k}-sitemap.xml`] = routeRules
+ for (const k in config.sitemaps) {
+ nuxt.options.nitro.routeRules[`/sitemap/${k}.xml`] = routeRules
+ nuxt.options.nitro.routeRules[`/${k}-sitemap.xml`] = { redirect: `/sitemap/${k}.xml` }
+ }
}
else {
// TODO we should support the chunked generated sitemap names
@@ -394,6 +396,14 @@ declare module 'vue-router' {
addServerHandler({
route: '/sitemap_index.xml',
handler: resolve('./runtime/nitro/routes/sitemap_index.xml'),
+ lazy: true,
+ middleware: false,
+ })
+ addServerHandler({
+ route: `/sitemap/**:sitemap`,
+ handler: resolve('./runtime/nitro/routes/sitemap/[sitemap].xml'),
+ lazy: true,
+ middleware: false,
})
sitemaps.index = {
sitemapName: 'index',
@@ -405,15 +415,11 @@ declare module 'vue-router' {
for (const sitemapName in config.sitemaps) {
if (sitemapName === 'index')
continue
- addServerHandler({
- route: `/${sitemapName}-sitemap.xml`,
- handler: resolve('./runtime/nitro/middleware/[sitemap]-sitemap.xml'),
- })
const definition = config.sitemaps[sitemapName] as MultiSitemapEntry[string]
sitemaps[sitemapName as keyof typeof sitemaps] = defu(
{
sitemapName,
- _route: withBase(`${sitemapName}-sitemap.xml`, nuxt.options.app.baseURL || '/'),
+ _route: withBase(`sitemap/${sitemapName}.xml`, nuxt.options.app.baseURL || '/'),
_hasSourceChunk: typeof definition.urls !== 'undefined' || definition.sources?.length || !!definition.dynamicUrlsApiEndpoint,
},
{ ...definition, urls: undefined, sources: undefined },
@@ -422,10 +428,7 @@ declare module 'vue-router' {
}
}
else {
- // we have to registrer it as a middleware we can't match the URL pattern
- addServerHandler({
- handler: resolve('./runtime/nitro/middleware/[sitemap]-sitemap.xml'),
- })
+ // we have to register it as a middleware we can't match the URL pattern
sitemaps.chunks = {
sitemapName: 'chunks',
defaults: config.defaults,
diff --git a/src/runtime/nitro/routes/sitemap.xsl.ts b/src/runtime/nitro/routes/sitemap.xsl.ts
index 1da87671..0aa79433 100644
--- a/src/runtime/nitro/routes/sitemap.xsl.ts
+++ b/src/runtime/nitro/routes/sitemap.xsl.ts
@@ -19,7 +19,8 @@ export default defineEventHandler(async (e) => {
const { name: siteName, url: siteUrl } = useSiteConfig(e)
const referrer = getHeader(e, 'Referer')! || '/'
- const isNotIndexButHasIndex = referrer !== fixPath('/sitemap.xml') && parseURL(referrer).pathname.endsWith('-sitemap.xml')
+ const referrerPath = parseURL(referrer).pathname
+ const isNotIndexButHasIndex = referrerPath !== '/sitemap.xml' && referrerPath !== '/sitemap_index.xml' && referrerPath.endsWith('.xml')
const sitemapName = parseURL(referrer).pathname.split('/').pop()?.split('-sitemap')[0] || fallbackSitemapName
const title = `${siteName}${sitemapName !== 'sitemap.xml' ? ` - ${sitemapName === 'sitemap_index.xml' ? 'index' : sitemapName}` : ''}`.replace(/&/g, '&')
diff --git a/src/runtime/nitro/middleware/[sitemap]-sitemap.xml.ts b/src/runtime/nitro/routes/sitemap/[sitemap].xml.ts
similarity index 51%
rename from src/runtime/nitro/middleware/[sitemap]-sitemap.xml.ts
rename to src/runtime/nitro/routes/sitemap/[sitemap].xml.ts
index 37ebd89e..e0467df3 100644
--- a/src/runtime/nitro/middleware/[sitemap]-sitemap.xml.ts
+++ b/src/runtime/nitro/routes/sitemap/[sitemap].xml.ts
@@ -1,22 +1,16 @@
-import { createError, defineEventHandler } from 'h3'
-import { parseURL } from 'ufo'
-import { useSimpleSitemapRuntimeConfig } from '../utils'
-import { createSitemap } from '../sitemap/nitro'
+import { createError, defineEventHandler, getRouterParam } from 'h3'
+import { useSimpleSitemapRuntimeConfig } from '../../utils'
+import { createSitemap } from '../../sitemap/nitro'
export default defineEventHandler(async (e) => {
- const path = parseURL(e.path).pathname
- if (!path.endsWith('-sitemap.xml'))
- return
-
- const runtimeConfig = useSimpleSitemapRuntimeConfig()
+ const runtimeConfig = useSimpleSitemapRuntimeConfig(e)
const { sitemaps } = runtimeConfig
- const sitemapName = path
- .replace('-sitemap.xml', '')
- .replace('/', '')
+ const sitemapName = (getRouterParam(e, 'sitemap') || e.path)?.replace('.xml', '')
+ .replace('/sitemap/', '')
// check if sitemapName can be cast to a number safely
const isChunking = typeof sitemaps.chunks !== 'undefined' && !Number.isNaN(Number(sitemapName))
- if (!(sitemapName in sitemaps) && !isChunking) {
+ if (!sitemapName || (!(sitemapName in sitemaps) && !isChunking)) {
return createError({
statusCode: 404,
message: `Sitemap "${sitemapName}" not found.`,
diff --git a/src/runtime/nitro/routes/sitemap_index.xml.ts b/src/runtime/nitro/routes/sitemap_index.xml.ts
index b653de61..5285f5f0 100644
--- a/src/runtime/nitro/routes/sitemap_index.xml.ts
+++ b/src/runtime/nitro/routes/sitemap_index.xml.ts
@@ -2,7 +2,7 @@ import { appendHeader, defineEventHandler, setHeader } from 'h3'
import { useSimpleSitemapRuntimeConfig } from '../utils'
import { buildSitemapIndex, urlsToIndexXml } from '../sitemap/builder/sitemap-index'
import type { SitemapOutputHookCtx } from '../../types'
-import { useNitroUrlResolvers } from '..//sitemap/nitro'
+import { useNitroUrlResolvers } from '../sitemap/nitro'
import { useNitroApp } from '#imports'
export default defineEventHandler(async (e) => {
@@ -18,7 +18,7 @@ export default defineEventHandler(async (e) => {
e,
'x-nitro-prerender',
sitemaps.filter(entry => !!entry._sitemapName)
- .map(entry => encodeURIComponent(`/${entry._sitemapName}-sitemap.xml`)).join(', '),
+ .map(entry => encodeURIComponent(`/sitemap/${entry._sitemapName}.xml`)).join(', '),
)
}
diff --git a/src/runtime/nitro/sitemap/builder/sitemap-index.ts b/src/runtime/nitro/sitemap/builder/sitemap-index.ts
index f881f035..cc306645 100644
--- a/src/runtime/nitro/sitemap/builder/sitemap-index.ts
+++ b/src/runtime/nitro/sitemap/builder/sitemap-index.ts
@@ -64,7 +64,7 @@ export async function buildSitemapIndex(resolvers: NitroUrlResolvers, runtimeCon
const sitemap = chunks[name]
const entry: SitemapIndexEntry = {
_sitemapName: name,
- sitemap: resolvers.canonicalUrlResolver(`${name}-sitemap.xml`),
+ sitemap: resolvers.canonicalUrlResolver(`sitemap/${name}.xml`),
}
let lastmod = sitemap.urls
.filter(a => !!a?.lastmod)
diff --git a/src/runtime/nitro/sitemap/nitro.ts b/src/runtime/nitro/sitemap/nitro.ts
index 1adfdbc9..e5d68b46 100644
--- a/src/runtime/nitro/sitemap/nitro.ts
+++ b/src/runtime/nitro/sitemap/nitro.ts
@@ -33,19 +33,19 @@ export function useNitroUrlResolvers(e: H3Event): NitroUrlResolvers {
}
}
-export async function createSitemap(e: H3Event, definition: SitemapDefinition, runtimeConfig: ModuleRuntimeConfig) {
+export async function createSitemap(event: H3Event, definition: SitemapDefinition, runtimeConfig: ModuleRuntimeConfig) {
const { sitemapName } = definition
const nitro = useNitroApp()
- const resolvers = useNitroUrlResolvers(e)
+ const resolvers = useNitroUrlResolvers(event)
let sitemapUrls = await buildSitemapUrls(definition, resolvers, runtimeConfig)
const routeRuleMatcher = createNitroRouteRuleMatcher()
const { autoI18n } = runtimeConfig
- sitemapUrls = sitemapUrls.map((e) => {
- // blocked by nuxt-simple-robots (this is a polyfill if not installed)
- if (!getPathRobotConfig(e, { path: e._path.pathname, skipSiteIndexable: true }).indexable)
+ sitemapUrls = sitemapUrls.map((u) => {
+ const path = u._path?.pathname || u.loc
+ // blocked by @nuxtjs/robots (this is a polyfill if not installed)
+ if (!getPathRobotConfig(event, { path, skipSiteIndexable: true }).indexable)
return false
- const path = e._path.pathname
let routeRules = routeRuleMatcher(path)
// apply top-level path without prefix, users can still target the localed path
if (autoI18n?.locales && autoI18n?.strategy !== 'no_prefix') {
@@ -70,7 +70,7 @@ export async function createSitemap(e: H3Event, definition: SitemapDefinition, r
if (routeRules.redirect || hasRobotsDisabled)
return false
- return routeRules.sitemap ? defu(e, routeRules.sitemap) as ResolvedSitemapUrl : e
+ return routeRules.sitemap ? defu(u, routeRules.sitemap) as ResolvedSitemapUrl : u
}).filter(Boolean)
// 6. nitro hooks
@@ -88,11 +88,11 @@ export async function createSitemap(e: H3Event, definition: SitemapDefinition, r
const ctx = { sitemap, sitemapName }
await nitro.hooks.callHook('sitemap:output', ctx)
// need to clone the config object to make it writable
- setHeader(e, 'Content-Type', 'text/xml; charset=UTF-8')
+ setHeader(event, 'Content-Type', 'text/xml; charset=UTF-8')
if (runtimeConfig.cacheMaxAgeSeconds)
- setHeader(e, 'Cache-Control', `public, max-age=${runtimeConfig.cacheMaxAgeSeconds}, must-revalidate`)
+ setHeader(event, 'Cache-Control', `public, max-age=${runtimeConfig.cacheMaxAgeSeconds}, must-revalidate`)
else
- setHeader(e, 'Cache-Control', `no-cache, no-store`)
- e.context._isSitemap = true
+ setHeader(event, 'Cache-Control', `no-cache, no-store`)
+ event.context._isSitemap = true
return ctx.sitemap
}
diff --git a/test/integration/chunks/default.ts b/test/integration/chunks/default.ts
index 1a9b95c0..7d35a1ea 100644
--- a/test/integration/chunks/default.ts
+++ b/test/integration/chunks/default.ts
@@ -17,20 +17,20 @@ describe('multi chunks', () => {
"
- https://nuxtseo.com/0-sitemap.xml
+ https://nuxtseo.com/sitemap/0.xml
- https://nuxtseo.com/1-sitemap.xml
+ https://nuxtseo.com/sitemap/1.xml
- https://nuxtseo.com/2-sitemap.xml
+ https://nuxtseo.com/sitemap/2.xml
- https://nuxtseo.com/3-sitemap.xml
+ https://nuxtseo.com/sitemap/3.xml
"
`)
- const sitemap0 = await $fetch('/0-sitemap.xml')
+ const sitemap0 = await $fetch('/sitemap/0.xml')
expect(sitemap0).toMatchInlineSnapshot(`
"
diff --git a/test/integration/chunks/generate.test.ts b/test/integration/chunks/generate.test.ts
index 440c33f2..49abd85c 100644
--- a/test/integration/chunks/generate.test.ts
+++ b/test/integration/chunks/generate.test.ts
@@ -27,20 +27,20 @@ describe('generate', () => {
"
- https://nuxtseo.com/0-sitemap.xml
+ https://nuxtseo.com/sitemap/0.xml
- https://nuxtseo.com/1-sitemap.xml
+ https://nuxtseo.com/sitemap/1.xml
- https://nuxtseo.com/2-sitemap.xml
+ https://nuxtseo.com/sitemap/2.xml
- https://nuxtseo.com/3-sitemap.xml
+ https://nuxtseo.com/sitemap/3.xml
"
`)
- const sitemapEn = (await readFile(resolve(rootDir, '.output/public/0-sitemap.xml'), 'utf-8')).replace(/lastmod>(.*?)<')
+ const sitemapEn = (await readFile(resolve(rootDir, '.output/public/sitemap/0.xml'), 'utf-8')).replace(/lastmod>(.*?)<')
expect(sitemapEn).toMatchInlineSnapshot(`
"
diff --git a/test/integration/i18n/domains.test.ts b/test/integration/i18n/domains.test.ts
index 4fb2bfcd..2ef7bc18 100644
--- a/test/integration/i18n/domains.test.ts
+++ b/test/integration/i18n/domains.test.ts
@@ -41,18 +41,18 @@ describe('i18n domains', () => {
"
- https://nuxtseo.com/en-US-sitemap.xml
+ https://nuxtseo.com/sitemap/en-US.xml
- https://nuxtseo.com/es-ES-sitemap.xml
+ https://nuxtseo.com/sitemap/es-ES.xml
- https://nuxtseo.com/fr-FR-sitemap.xml
+ https://nuxtseo.com/sitemap/fr-FR.xml
"
`)
- const fr = await $fetch('/fr-FR-sitemap.xml')
+ const fr = await $fetch('/sitemap/fr-FR.xml')
expect(fr).toMatchInlineSnapshot(`
"
diff --git a/test/integration/i18n/dynamic-urls.test.ts b/test/integration/i18n/dynamic-urls.test.ts
index 54157c53..11e6dac7 100644
--- a/test/integration/i18n/dynamic-urls.test.ts
+++ b/test/integration/i18n/dynamic-urls.test.ts
@@ -24,7 +24,7 @@ await setup({
})
describe('i18n dynamic urls', () => {
it('basic', async () => {
- let sitemap = await $fetch('/en-US-sitemap.xml')
+ let sitemap = await $fetch('/sitemap/en-US.xml')
// strip lastmod
sitemap = sitemap.replace(/.*<\/lastmod>/g, '')
diff --git a/test/integration/i18n/filtering-include.test.ts b/test/integration/i18n/filtering-include.test.ts
index 9fff4f33..de2f2196 100644
--- a/test/integration/i18n/filtering-include.test.ts
+++ b/test/integration/i18n/filtering-include.test.ts
@@ -19,7 +19,7 @@ await setup({
})
describe('i18n filtering with include', () => {
it('basic', async () => {
- const sitemap = await $fetch('/main-sitemap.xml')
+ const sitemap = await $fetch('/sitemap/main.xml')
expect(sitemap).toMatchInlineSnapshot(`
"
diff --git a/test/integration/i18n/filtering-regexp.test.ts b/test/integration/i18n/filtering-regexp.test.ts
index b763d178..e25f80ce 100644
--- a/test/integration/i18n/filtering-regexp.test.ts
+++ b/test/integration/i18n/filtering-regexp.test.ts
@@ -21,7 +21,7 @@ await setup({
})
describe('i18n filtering with regexp', () => {
it('basic', async () => {
- let sitemap = await $fetch('/en-US-sitemap.xml')
+ let sitemap = await $fetch('/sitemap/en-US.xml')
// strip lastmod
sitemap = sitemap.replace(/.*<\/lastmod>/g, '')
diff --git a/test/integration/i18n/filtering.test.ts b/test/integration/i18n/filtering.test.ts
index f51084d4..a75cc131 100644
--- a/test/integration/i18n/filtering.test.ts
+++ b/test/integration/i18n/filtering.test.ts
@@ -16,7 +16,7 @@ await setup({
})
describe('i18n filtering', () => {
it('basic', async () => {
- let sitemap = await $fetch('/en-US-sitemap.xml')
+ let sitemap = await $fetch('/sitemap/en-US.xml')
// strip lastmod
sitemap = sitemap.replace(/.*<\/lastmod>/g, '')
diff --git a/test/integration/i18n/generate.test.ts b/test/integration/i18n/generate.test.ts
index 870b83a9..114cdabe 100644
--- a/test/integration/i18n/generate.test.ts
+++ b/test/integration/i18n/generate.test.ts
@@ -27,17 +27,17 @@ describe('generate', () => {
"
- https://nuxtseo.com/en-US-sitemap.xml
+ https://nuxtseo.com/sitemap/en-US.xml
- https://nuxtseo.com/es-ES-sitemap.xml
+ https://nuxtseo.com/sitemap/es-ES.xml
- https://nuxtseo.com/fr-FR-sitemap.xml
+ https://nuxtseo.com/sitemap/fr-FR.xml
"
`)
- const sitemapEn = (await readFile(resolve(rootDir, '.output/public/en-US-sitemap.xml'), 'utf-8')).replace(/lastmod>(.*?)<')
+ const sitemapEn = (await readFile(resolve(rootDir, '.output/public/sitemap/en-US.xml'), 'utf-8')).replace(/lastmod>(.*?)<')
expect(sitemapEn).toMatchInlineSnapshot(`
"
diff --git a/test/integration/i18n/pages-multi.test.ts b/test/integration/i18n/pages-multi.test.ts
index c574fe5e..ef3c3950 100644
--- a/test/integration/i18n/pages-multi.test.ts
+++ b/test/integration/i18n/pages-multi.test.ts
@@ -6,13 +6,22 @@ const { resolve } = createResolver(import.meta.url)
await setup({
rootDir: resolve('../../fixtures/i18n'),
- build: true,
server: true,
nuxtConfig: {
i18n: {
locales: [
- 'en',
- 'fr',
+ {
+ code: 'en',
+ iso: 'en-US',
+ },
+ {
+ code: 'es',
+ iso: 'es-ES',
+ },
+ {
+ code: 'fr',
+ iso: 'fr-FR',
+ },
],
pages: {
'about': {
@@ -49,22 +58,22 @@ await setup({
})
describe('i18n pages multi', () => {
it('basic', async () => {
- const index = await $fetch('/sitemap.xml')
+ const index = await $fetch('/sitemap_index.xml')
expect(index).toMatchInlineSnapshot(`
"
- https://nuxtseo.com/en-US-sitemap.xml
+ https://nuxtseo.com/sitemap/en-US.xml
- https://nuxtseo.com/fr-FR-sitemap.xml
+ https://nuxtseo.com/sitemap/es-ES.xml
- https://nuxtseo.com/es-ES-sitemap.xml
+ https://nuxtseo.com/sitemap/fr-FR.xml
"
`)
- const fr = await $fetch('/fr-FR-sitemap.xml')
+ const fr = await $fetch('/sitemap/fr-FR.xml')
expect(fr).toMatchInlineSnapshot(`
"
@@ -85,8 +94,8 @@ describe('i18n pages multi', () => {
weekly
-
+
https://nuxtseo.com/fr/offres/developement
@@ -114,19 +123,5 @@ describe('i18n pages multi', () => {
"
`)
- const es = await $fetch('/es-ES-sitemap.xml')
- expect(es).toMatchInlineSnapshot(`
- "
-
-
- https://nuxtseo.com/es/__sitemap/url
- weekly
-
-
-
-
-
- "
- `)
}, 60000)
})
diff --git a/test/integration/multi/defaults.ts b/test/integration/multi/defaults.ts
index 6516522e..2fa94083 100644
--- a/test/integration/multi/defaults.ts
+++ b/test/integration/multi/defaults.ts
@@ -36,7 +36,7 @@ await setup({
})
describe('mutli defaults', () => {
it('basic', async () => {
- let sitemap = await $fetch('/foo-sitemap.xml')
+ let sitemap = await $fetch('/sitemap/foo.xml')
// remove lastmods before tresting
sitemap = sitemap.replace(/lastmod>(.*?)<')
// basic test to make sure we get a valid response
diff --git a/test/integration/multi/endpoints.ts b/test/integration/multi/endpoints.ts
index 2e0bead0..e7bea1e8 100644
--- a/test/integration/multi/endpoints.ts
+++ b/test/integration/multi/endpoints.ts
@@ -25,7 +25,7 @@ await setup({
})
describe('multi endpoints', () => {
it('basic', async () => {
- let sitemap = await $fetch('/foo-sitemap.xml')
+ let sitemap = await $fetch('/sitemap/foo.xml')
// remove lastmods before tresting
sitemap = sitemap.replace(/lastmod>(.*?)<')
// basic test to make sure we get a valid response
diff --git a/test/integration/multi/filtering.test.ts b/test/integration/multi/filtering.test.ts
index 9e6cd716..c22b31a0 100644
--- a/test/integration/multi/filtering.test.ts
+++ b/test/integration/multi/filtering.test.ts
@@ -36,7 +36,7 @@ await setup({
})
describe('multi filtering', () => {
it('basic', async () => {
- let sitemap = await $fetch('/foo-sitemap.xml')
+ let sitemap = await $fetch('/sitemap/foo.xml')
// strip lastmod
sitemap = sitemap.replace(/.*<\/lastmod>/g, '')