@nuxtjs/sitemap
Use Nuxt ^4.6.0 || ^5.0.0 and Node ^22.22.3 || ^24.15.0 || >=26.0.0.
The module serves /sitemap.xml, built from app pages, route rules, prerendered routes, and your sources.
Docs: https://nuxtseo.com/docs/sitemap
Setup
Install with npx nuxt module add sitemap. The config key is sitemap.
Included in @nuxtjs/seo v6. If the app uses @nuxtjs/seo, configure the sitemap key and do not add the module again.
Set site.url. Every <loc> resolves against it.
If site.url is missing, the production server uses the request Host header. The build gives no warning.
In dev, <loc> always uses the dev server host. This is expected.
If @nuxtjs/robots is installed, the module adds the sitemap to robots.txt. It uses /sitemap_index.xml when there are multiple sitemaps.
Automatic behaviour
- Every static page is listed. Dynamic pages, such as
pages/blog/[slug].vue, are not listed. Add them with a source. - Prerendered routes are added, including routes the prerender crawler finds.
- A route with the
robots: falseroute rule is dropped. So is arobotsstring withnoindexornone. - When a page is prerendered,
<img>and<video>inside<main>become image and video entries.article:modified_timebecomeslastmod. - A content page gets
lastmodfrom frontmatter:sitemap.lastmod, then a modification date (updatedAt,modifiedAt), then a publication date (publishedAt,date).contentLastmod: falseturns this off. - Entries are sorted by path depth, then alphabetically. Set
sortEntries: falseto keep source order. - An invalid
lastmodis dropped from the entry. Dev prints an XML comment in its place. - In production, the resolved sitemap is cached for
cacheMaxAgeSeconds(600 by default). Dev and prerender do not cache.
Add dynamic URLs
Return the entries from a server route, then list the route in sources.
Import defineSitemapEventHandler() from #sitemap/server. Server auto imports also resolve it. It adds types only, no cache.
// server/api/__sitemap__/urls.ts
import { defineSitemapEventHandler } from '#sitemap/server'
export default defineSitemapEventHandler(async () => {
const posts = await $fetch<{ slug: string, updatedAt: string }[]>('https://cms.example.com/posts')
return posts.map(post => ({
loc: `/blog/${post.slug}`, // a path; it resolves against site.url
lastmod: post.updatedAt,
}))
})// nuxt.config.ts
export default defineNuxtConfig({
sitemap: {
sources: ['/api/__sitemap__/urls'],
},
})A source can also be a remote JSON or XML URL, or [url, { headers }] for an authenticated API.
If a CMS returns encoded paths, set _encoded: true on each entry to stop double encoding.
urls in the config also adds entries, but the function runs once at build time. Use sources for data that changes after deploy.
Remove or tune one page
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
'/checkout/**': { robots: false }, // drop from the sitemap
'/legal': { sitemap: false }, // drop from the sitemap only
'/about': { sitemap: { priority: 0.3, changefreq: 'yearly' } },
},
sitemap: {
exclude: ['/admin/**', /^\/preview-/],
},
})definePageMeta({ sitemap: { priority: 0.8 } }) sets values for that page. definePageMeta({ sitemap: false }) removes it, also when it is prerendered.
Multiple sitemaps
// nuxt.config.ts
export default defineNuxtConfig({
sitemap: {
sitemaps: {
pages: { includeAppSources: true, exclude: ['/blog/**'] },
posts: { sources: ['/api/__sitemap__/posts'], chunks: 5000 },
},
},
})- The index is
/sitemap_index.xml. On a server,/sitemap.xmlredirects to it. A static build writes the index to/sitemap.xmlas well. - Child sitemaps are at
/__sitemap__/<name>.xml. Chunks are/__sitemap__/posts-0.xml,posts-1.xml, and so on. sitemapsPathPrefix: '/'moves them to the root:/pages.xml,/posts-0.xml.- App sources (pages, route rules, prerender) go only to a sitemap with
includeAppSources: true. - Top level
sourcesalso go only to sitemaps withincludeAppSources: true. - An entry with
_sitemap: 'posts'goes only to that sitemap. An unknown name logs an error and drops the entry. sitemaps: truesplits every URL into numbered chunks ofdefaultSitemapsChunkSize(1000).
Prerender and zero runtime
To prerender the sitemap, add /sitemap.xml to nitro.prerender.routes. nuxt generate does this for you.
zeroRuntime: true prerenders every sitemap and removes the sitemap code from the server bundle.
In both modes, sources are fetched once at build. New CMS entries do not appear until the next build.
Integrations
- Nuxt Content v3: a collection needs
defineSitemapSchema()in its schema, or it is not in the sitemap. See references/nuxt-content.md. @nuxtjs/i18n: with a prefix strategy, each locale gets its own sitemap, withhreflangalternatives andx-default. The sitemap name is the localelanguageif it is set, else the localecode. Withlanguage: 'en-US', the path is/__sitemap__/en-US.xml. A dynamic entry goes only to the default locale sitemap. Set_i18nTransform: trueto add it to every locale with its prefix._sitemaptakes the sitemap name._sitemap: 'en'fails when the sitemap isen-US. SetautoI18n: falsefor one sitemap without locales.
Traps
- String filters match whole path segments only.
exclude: ['/blog/draft-*']removes nothing. Use a RegExp such as/^\/blog\/draft-/. - A dynamic route is not in the sitemap until a source,
urls, or the prerender crawler provides its URLs. - A prerendered or
zeroRuntimesitemap never refetches sources. Use the runtime server for CMS data that changes between deploys. - Nuxt Content
filterandonUrlcannot read outside variables. The build fails and names the variable. See references/nuxt-content.md. lastmodset to the current date on every build tells crawlers nothing. Use a real content update time.
Version limits
v9 has these breaking changes. Each line gives the fix.
- Nuxt
^4.6.0 || ^5.0.0and Node^22.22.3 || ^24.15.0 || >=26.0.0are required. Upgrade both before the module. - Hook contexts receive a
RequestEventfromnuxt/server.event.node.reqdoes not exist there. Read a header withctx.event.req.headers.get('authorization'). defineSitemapEventHandler()receives aRequestEventthat is also typed as the server builder'sH3Event. h3 helpers and utilities typed againstH3Eventaccept it.event.reqis the webRequest.- Import
defineSitemapEventHandler()andasSitemapUrl()from#sitemap/server. ImportgetQuery()and other request utilities fromnuxt/server. @nuxtjs/robotsmust be v7.nuxt-site-configmust be v5. Upgrade them together with the sitemap.- The optional
zodpeer is^4.6.5. Use Zod 4 withdefineSitemapSchema(). - Nuxt Content v2 is not supported. Upgrade to Nuxt Content v3 and add
defineSitemapSchema()to each collection. - The DevTools Sitemap tab needs the
nuxtseo-devtools-hostdev dependency.
Config
sources,urls,include,exclude,defaults: top level for one sitemap. Withsitemaps, set them per sitemap.excludeAppSources:true, or names such as['nuxt:pages', 'nuxt:prerender'].sitemapName(sitemap.xml): the path of the single sitemap. Ignored withsitemaps.cacheMaxAgeSeconds(600): server cache andCache-Control.falseturns both off.xsl(/__sitemap__/style.xsl): the browser view. Setfalsefor raw XML. Browsers show raw i18n XML as text; that is a browser bug.- Other options: https://nuxtseo.com/docs/sitemap/api/config
Nitro hooks change output at runtime: sitemap:sources (add sources or request headers), sitemap:input, sitemap:resolved, sitemap:output, and sitemap:sitemaps-resolved (register sitemaps at runtime). See https://nuxtseo.com/docs/sitemap/nitro-api/nitro-hooks
Debug
/__sitemap__/debug.jsonlists every source, its URLs, and fetch errors. It exists in dev, or in production withdebug: true.zeroRuntimeremoves it.- Nuxt DevTools has a Sitemap tab with the same data. The tab needs
nuxtseo-devtools-hostas a dev dependency:pnpm add -D nuxtseo-devtools-host. - For a prerendered sitemap, set
debug: trueand read.output/public/__sitemap__/debug.json.