When working with a CMS or external data sources, you may need to generate sitemap URLs dynamically at runtime.
The module supports two types of data sources:
If you have an existing XML sitemap, you can reference it directly in your configuration:
export default defineNuxtConfig({
sitemap: {
sources: [
'https://example.com/sitemap.xml',
]
}
})
When fetching dynamic URLs from external APIs, you have two main approaches:
For APIs that require authentication or custom headers, provide sources as an array with fetch options:
export default defineNuxtConfig({
sitemap: {
sources: [
// Unauthenticated endpoint
'https://api.example.com/pages/urls',
// Authenticated endpoint
[
'https://authenticated-api.example.com/pages/urls',
{ headers: { Authorization: 'Bearer <token>' } }
]
]
}
})
Step 1: Create the API endpoint
Use the defineSitemapEventHandler() helper to create type-safe sitemap endpoints:
// server/api/__sitemap__/urls.ts
import { defineSitemapEventHandler } from '#imports'
import type { SitemapUrlInput } from '#sitemap/types'
export default defineSitemapEventHandler(() => {
return [
{
loc: '/about-us',
// Specify which sitemap this URL belongs to
_sitemap: 'pages',
},
] satisfies SitemapUrlInput[]
})
// server/api/__sitemap__/urls.ts
import { defineSitemapEventHandler } from '#imports'
import type { SitemapUrl } from '#sitemap/types'
export default defineSitemapEventHandler(async () => {
const [posts, pages] = await Promise.all([
$fetch<{ path: string, slug: string }[]>('https://api.example.com/posts')
.then(posts => posts.map(p => ({
loc: `/blog/${p.slug}`, // Transform to your domain structure
_sitemap: 'posts',
} satisfies SitemapUrl))),
$fetch<{ path: string }[]>('https://api.example.com/pages')
.then(pages => pages.map(p => ({
loc: p.path,
_sitemap: 'pages',
} satisfies SitemapUrl))),
])
return [...posts, ...pages]
})
// server/api/__sitemap__/wordpress.ts
import { defineSitemapEventHandler } from '#imports'
export default defineSitemapEventHandler(async () => {
const posts = await $fetch('https://api.externalwebsite.com/wp-json/wp/v2/posts')
return posts.map(post => ({
// Transform external URL to your domain
loc: `/blog/${post.slug}`, // NOT post.link
lastmod: post.modified,
changefreq: 'weekly',
priority: 0.7,
}))
})
// server/api/__sitemap__/urls.ts
import { defineSitemapEventHandler } from '#imports'
import type { SitemapUrl } from '#sitemap/types'
export default defineSitemapEventHandler(async () => {
const config = useRuntimeConfig()
const baseUrl = config.public.siteUrl
const locales = config.public.i18n.locales.map(locale => locale.code)
const isoLocales = Object.fromEntries(
config.public.i18n.locales.map(locale => ([locale.code, locale.iso]))
)
// Example: Fetch data for each locale
const apiQueries = locales.map(locale =>
$fetch(`${config.public.apiEndpoint}/sitemap/${locale}/products`)
)
const sitemaps = await Promise.all(apiQueries)
return sitemaps.flat().map(entry => ({
// explicit sitemap mapping
_sitemap: isoLocales[entry.locale],
loc: `${baseUrl}/${entry.locale}/product/${entry.url}`,
alternatives: entry.alternates?.map(alt => ({
hreflang: isoLocales[alt.locale],
href: `${baseUrl}/${alt.locale}/product/${alt.url}`
}))
} satisfies SitemapUrl))
})
Step 2: Configure the endpoint
Add your custom endpoint to the sitemap configuration:
export default defineNuxtConfig({
sitemap: {
sources: [
'/api/__sitemap__/urls',
]
}
})
export default defineNuxtConfig({
sitemap: {
sitemaps: {
posts: {
sources: [
'/api/__sitemap__/urls/posts',
]
},
pages: {
sources: [
'/api/__sitemap__/urls/pages',
]
}
}
}
})
By default, the module automatically encodes URL paths. This handles special characters like spaces and unicode (e.g., emojis, accented characters).
If your API or CMS returns URLs that are already encoded, mark them with _encoded: true to prevent double-encoding.
import { defineSitemapEventHandler } from '#imports'
export default defineSitemapEventHandler(async () => {
// URLs from your API are already encoded
const urls = await $fetch<{ path: string }[]>('https://api.example.com/pages')
// e.g. [{ path: '/products/%24pecial-offer' }, { path: '/blog/%F0%9F%98%85' }]
return urls.map(url => ({
loc: url.path,
_encoded: true,
}))
})
_encoded: true is set, the module skips automatic encoding entirely. Make sure your URLs are properly encoded.