Skip to content

Commit

Permalink
feat!: new multi sitemaps paths (#320)
Browse files Browse the repository at this point in the history
  • Loading branch information
harlan-zw authored Jul 21, 2024
1 parent a7c04bc commit bb7d9c7
Show file tree
Hide file tree
Showing 18 changed files with 80 additions and 87 deletions.
25 changes: 14 additions & 11 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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',
Expand All @@ -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 },
Expand All @@ -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,
Expand Down
3 changes: 2 additions & 1 deletion src/runtime/nitro/routes/sitemap.xsl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, '&')

Expand Down
Original file line number Diff line number Diff line change
@@ -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.`,
Expand Down
4 changes: 2 additions & 2 deletions src/runtime/nitro/routes/sitemap_index.xml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand All @@ -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(', '),
)
}

Expand Down
2 changes: 1 addition & 1 deletion src/runtime/nitro/sitemap/builder/sitemap-index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
22 changes: 11 additions & 11 deletions src/runtime/nitro/sitemap/nitro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand All @@ -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
Expand All @@ -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
}
10 changes: 5 additions & 5 deletions test/integration/chunks/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,20 @@ describe('multi chunks', () => {
"<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet type="text/xsl" href="/__sitemap__/style.xsl"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap>
<loc>https://nuxtseo.com/0-sitemap.xml</loc>
<loc>https://nuxtseo.com/sitemap/0.xml</loc>
</sitemap>
<sitemap>
<loc>https://nuxtseo.com/1-sitemap.xml</loc>
<loc>https://nuxtseo.com/sitemap/1.xml</loc>
</sitemap>
<sitemap>
<loc>https://nuxtseo.com/2-sitemap.xml</loc>
<loc>https://nuxtseo.com/sitemap/2.xml</loc>
</sitemap>
<sitemap>
<loc>https://nuxtseo.com/3-sitemap.xml</loc>
<loc>https://nuxtseo.com/sitemap/3.xml</loc>
</sitemap>
</sitemapindex>"
`)
const sitemap0 = await $fetch('/0-sitemap.xml')
const sitemap0 = await $fetch('/sitemap/0.xml')
expect(sitemap0).toMatchInlineSnapshot(`
"<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet type="text/xsl" href="/__sitemap__/style.xsl"?>
<urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd" xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
Expand Down
10 changes: 5 additions & 5 deletions test/integration/chunks/generate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,20 @@ describe('generate', () => {
"<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet type="text/xsl" href="/__sitemap__/style.xsl"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap>
<loc>https://nuxtseo.com/0-sitemap.xml</loc>
<loc>https://nuxtseo.com/sitemap/0.xml</loc>
</sitemap>
<sitemap>
<loc>https://nuxtseo.com/1-sitemap.xml</loc>
<loc>https://nuxtseo.com/sitemap/1.xml</loc>
</sitemap>
<sitemap>
<loc>https://nuxtseo.com/2-sitemap.xml</loc>
<loc>https://nuxtseo.com/sitemap/2.xml</loc>
</sitemap>
<sitemap>
<loc>https://nuxtseo.com/3-sitemap.xml</loc>
<loc>https://nuxtseo.com/sitemap/3.xml</loc>
</sitemap>
</sitemapindex>"
`)
const sitemapEn = (await readFile(resolve(rootDir, '.output/public/0-sitemap.xml'), 'utf-8')).replace(/lastmod>(.*?)</g, 'lastmod><')
const sitemapEn = (await readFile(resolve(rootDir, '.output/public/sitemap/0.xml'), 'utf-8')).replace(/lastmod>(.*?)</g, 'lastmod><')
expect(sitemapEn).toMatchInlineSnapshot(`
"<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet type="text/xsl" href="/__sitemap__/style.xsl"?>
<urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd" xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
Expand Down
8 changes: 4 additions & 4 deletions test/integration/i18n/domains.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,18 @@ describe('i18n domains', () => {
"<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet type="text/xsl" href="/__sitemap__/style.xsl"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap>
<loc>https://nuxtseo.com/en-US-sitemap.xml</loc>
<loc>https://nuxtseo.com/sitemap/en-US.xml</loc>
</sitemap>
<sitemap>
<loc>https://nuxtseo.com/es-ES-sitemap.xml</loc>
<loc>https://nuxtseo.com/sitemap/es-ES.xml</loc>
</sitemap>
<sitemap>
<loc>https://nuxtseo.com/fr-FR-sitemap.xml</loc>
<loc>https://nuxtseo.com/sitemap/fr-FR.xml</loc>
</sitemap>
</sitemapindex>"
`)

const fr = await $fetch('/fr-FR-sitemap.xml')
const fr = await $fetch('/sitemap/fr-FR.xml')
expect(fr).toMatchInlineSnapshot(`
"<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet type="text/xsl" href="/__sitemap__/style.xsl"?>
<urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd" xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
Expand Down
2 changes: 1 addition & 1 deletion test/integration/i18n/dynamic-urls.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>.*<\/lastmod>/g, '')
Expand Down
2 changes: 1 addition & 1 deletion test/integration/i18n/filtering-include.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(`
"<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet type="text/xsl" href="/__sitemap__/style.xsl"?>
Expand Down
2 changes: 1 addition & 1 deletion test/integration/i18n/filtering-regexp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>.*<\/lastmod>/g, '')
Expand Down
2 changes: 1 addition & 1 deletion test/integration/i18n/filtering.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>.*<\/lastmod>/g, '')
Expand Down
8 changes: 4 additions & 4 deletions test/integration/i18n/generate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,17 @@ describe('generate', () => {
"<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet type="text/xsl" href="/__sitemap__/style.xsl"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap>
<loc>https://nuxtseo.com/en-US-sitemap.xml</loc>
<loc>https://nuxtseo.com/sitemap/en-US.xml</loc>
</sitemap>
<sitemap>
<loc>https://nuxtseo.com/es-ES-sitemap.xml</loc>
<loc>https://nuxtseo.com/sitemap/es-ES.xml</loc>
</sitemap>
<sitemap>
<loc>https://nuxtseo.com/fr-FR-sitemap.xml</loc>
<loc>https://nuxtseo.com/sitemap/fr-FR.xml</loc>
</sitemap>
</sitemapindex>"
`)
const sitemapEn = (await readFile(resolve(rootDir, '.output/public/en-US-sitemap.xml'), 'utf-8')).replace(/lastmod>(.*?)</g, 'lastmod><')
const sitemapEn = (await readFile(resolve(rootDir, '.output/public/sitemap/en-US.xml'), 'utf-8')).replace(/lastmod>(.*?)</g, 'lastmod><')
expect(sitemapEn).toMatchInlineSnapshot(`
"<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet type="text/xsl" href="/__sitemap__/style.xsl"?>
<urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd http://www.google.com/schemas/sitemap-image/1.1 http://www.google.com/schemas/sitemap-image/1.1/sitemap-image.xsd" xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
Expand Down
Loading

0 comments on commit bb7d9c7

Please sign in to comment.