Repository: nuxt-modules/sitemap Branch: main Commit: 2ed88043b08b Files: 422 Total size: 842.4 KB Directory structure: gitextract_wdbjvyab/ ├── .attw.json ├── .claude/ │ └── skills/ │ ├── nuxt-site-config-skilld/ │ │ └── SKILL.md │ ├── nuxt-test-utils-skilld/ │ │ └── SKILL.md │ ├── skilld-lock.yaml │ └── vitest-skilld/ │ └── SKILL.md ├── .editorconfig ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── 01-feature-suggestion.yml │ │ ├── 02-bug-report.yml │ │ ├── 03-documentation.yml │ │ ├── 04-help-wanted.yml │ │ └── config.yml │ ├── pull_request_template.md │ ├── renovate.json5 │ └── workflows/ │ ├── nightly.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── .npmrc ├── .nuxtrc ├── LICENSE.md ├── README.md ├── SECURITY.md ├── benchmark/ │ ├── app/ │ │ └── app.vue │ ├── bench.mjs │ ├── nuxt.config.ts │ ├── package.json │ └── server/ │ └── api/ │ └── ping.get.ts ├── build.config.ts ├── devtools/ │ ├── app.config.ts │ ├── app.vue │ ├── components/ │ │ └── Source.vue │ ├── composables/ │ │ ├── rpc.ts │ │ └── state.ts │ ├── nuxt.config.ts │ ├── package.json │ ├── pages/ │ │ ├── app-sources.vue │ │ ├── debug.vue │ │ ├── docs.vue │ │ ├── index.vue │ │ └── user-sources.vue │ └── tsconfig.json ├── docs/ │ └── content/ │ ├── 0.getting-started/ │ │ ├── 0.introduction.md │ │ ├── 1.installation.md │ │ ├── 2.data-sources.md │ │ └── 3.troubleshooting.md │ ├── 1.guides/ │ │ ├── 0.dynamic-urls.md │ │ ├── 1.filtering-urls.md │ │ ├── 2.multi-sitemaps.md │ │ ├── 3.i18n.md │ │ ├── 4.content.md │ │ ├── 5.prerendering.md │ │ ├── 6.best-practices.md │ │ ├── 7.submitting-sitemap.md │ │ └── 8.zero-runtime.md │ ├── 2.advanced/ │ │ ├── 0.loc-data.md │ │ ├── 1.images-videos.md │ │ ├── 2.performance.md │ │ ├── 3.chunking-sources.md │ │ └── 4.customising-ui.md │ ├── 4.api/ │ │ ├── 0.config.md │ │ └── 1.nuxt-hooks.md │ ├── 5.nitro-api/ │ │ └── nitro-hooks.md │ └── 5.releases/ │ ├── 3.v8.md │ ├── 4.v7.md │ ├── 5.v6.md │ ├── 6.v5.md │ ├── 7.v4.md │ └── 8.v3.md ├── eslint.config.mjs ├── examples/ │ ├── basic/ │ │ ├── app/ │ │ │ ├── app.vue │ │ │ └── pages/ │ │ │ ├── about.vue │ │ │ ├── contact.vue │ │ │ └── index.vue │ │ ├── nuxt.config.ts │ │ ├── package.json │ │ └── tsconfig.json │ ├── dynamic-urls/ │ │ ├── app/ │ │ │ ├── app.vue │ │ │ └── pages/ │ │ │ ├── blog/ │ │ │ │ └── [slug].vue │ │ │ └── index.vue │ │ ├── nuxt.config.ts │ │ ├── package.json │ │ ├── server/ │ │ │ └── api/ │ │ │ └── _sitemap-urls.ts │ │ └── tsconfig.json │ └── i18n/ │ ├── app/ │ │ ├── app.vue │ │ └── pages/ │ │ ├── about.vue │ │ ├── contact.vue │ │ └── index.vue │ ├── nuxt.config.ts │ ├── package.json │ └── tsconfig.json ├── package.json ├── patches/ │ └── @nuxtjs__mdc.patch ├── playground/ │ ├── .nuxtrc │ ├── app.vue │ ├── assets/ │ │ └── css/ │ │ └── main.css │ ├── content/ │ │ ├── _partial.md │ │ ├── bar.md │ │ ├── foo.md │ │ └── posts/ │ │ ├── bar.md │ │ └── foo.md │ ├── nuxt.config.ts │ ├── pages/ │ │ ├── .ignored/ │ │ │ └── test.vue │ │ ├── [...slug].vue │ │ ├── _dir/ │ │ │ └── robots.txt │ │ ├── about.vue │ │ ├── api/ │ │ │ └── foo.vue │ │ ├── blocked-by-robots-txt/ │ │ │ └── foo.vue │ │ ├── blog/ │ │ │ ├── [id].vue │ │ │ ├── categories.vue │ │ │ ├── index.vue │ │ │ ├── tags/ │ │ │ │ ├── edit.vue │ │ │ │ └── new.vue │ │ │ └── tags.vue │ │ ├── blog.vue │ │ ├── foo.bar.vue │ │ ├── hidden-path-but-in-sitemap/ │ │ │ └── index.vue │ │ ├── hide-me.vue │ │ ├── ignore-foo.vue │ │ ├── index.vue │ │ ├── new-page.vue │ │ ├── prerender-video.vue │ │ ├── prerender.vue │ │ ├── secret.vue │ │ └── users-[group]/ │ │ ├── [id].vue │ │ └── index.vue │ ├── server/ │ │ ├── api/ │ │ │ ├── _sitemap-urls.ts │ │ │ ├── fetch.ts │ │ │ ├── multi-sitemap-sources/ │ │ │ │ ├── bar.ts │ │ │ │ └── foo.ts │ │ │ ├── prerendered.ts │ │ │ ├── sitemap-bar.ts │ │ │ ├── sitemap-foo.ts │ │ │ └── sitemap-urls-to-be-confumsed-by-fetch.ts │ │ ├── plugins/ │ │ │ └── sitemap.ts │ │ ├── routes/ │ │ │ └── __sitemap.ts │ │ └── tsconfig.json │ └── tsconfig.json ├── pnpm-workspace.yaml ├── src/ │ ├── content.ts │ ├── devtools.ts │ ├── module.ts │ ├── prerender.ts │ ├── runtime/ │ │ ├── server/ │ │ │ ├── composables/ │ │ │ │ ├── asSitemapUrl.ts │ │ │ │ └── defineSitemapEventHandler.ts │ │ │ ├── content-compat.ts │ │ │ ├── kit.ts │ │ │ ├── plugins/ │ │ │ │ ├── compression.ts │ │ │ │ ├── nuxt-content-v2.ts │ │ │ │ └── warm-up.ts │ │ │ ├── robots-polyfill/ │ │ │ │ └── getPathRobotConfig.ts │ │ │ ├── routes/ │ │ │ │ ├── __sitemap__/ │ │ │ │ │ ├── debug-production.ts │ │ │ │ │ ├── debug.ts │ │ │ │ │ ├── nuxt-content-urls-v2.ts │ │ │ │ │ └── nuxt-content-urls-v3.ts │ │ │ │ ├── __zero-runtime/ │ │ │ │ │ ├── sitemap/ │ │ │ │ │ │ └── [sitemap].xml.ts │ │ │ │ │ ├── sitemap.xml.ts │ │ │ │ │ └── sitemap_index.xml.ts │ │ │ │ ├── sitemap/ │ │ │ │ │ └── [sitemap].xml.ts │ │ │ │ ├── sitemap.xml.ts │ │ │ │ ├── sitemap.xsl.ts │ │ │ │ └── sitemap_index.xml.ts │ │ │ ├── sitemap/ │ │ │ │ ├── builder/ │ │ │ │ │ ├── sitemap-index.ts │ │ │ │ │ ├── sitemap.ts │ │ │ │ │ └── xml.ts │ │ │ │ ├── event-handlers.ts │ │ │ │ ├── nitro.ts │ │ │ │ ├── urlset/ │ │ │ │ │ ├── normalise.ts │ │ │ │ │ ├── sort.ts │ │ │ │ │ └── sources.ts │ │ │ │ └── utils/ │ │ │ │ └── chunk.ts │ │ │ ├── tsconfig.json │ │ │ └── utils.ts │ │ ├── types.ts │ │ └── utils-pure.ts │ ├── templates.ts │ ├── utils/ │ │ ├── index.ts │ │ ├── parseHtmlExtractSitemapMeta.ts │ │ ├── parseSitemapIndex.ts │ │ └── parseSitemapXml.ts │ └── utils-internal/ │ ├── filter.ts │ ├── i18n.ts │ ├── kit.ts │ └── nuxtSitemap.ts ├── test/ │ ├── bench/ │ │ ├── i18n.bench.ts │ │ ├── normalize.bench.ts │ │ ├── sitemap.bench.ts │ │ └── xml.bench.ts │ ├── e2e/ │ │ ├── chunks/ │ │ │ ├── cache-headers.test.ts │ │ │ ├── chunk-count.test.ts │ │ │ ├── default.ts │ │ │ ├── generate.test.ts │ │ │ └── memoization.test.ts │ │ ├── content-v3/ │ │ │ ├── default.test.ts │ │ │ ├── define-schema.test.ts │ │ │ ├── filtering.test.ts │ │ │ ├── i18n.test.ts │ │ │ └── yaml-json.test.ts │ │ ├── global-setup.ts │ │ ├── hooks/ │ │ │ └── sources-hook-simple.test.ts │ │ ├── i18n/ │ │ │ ├── custom-paths-no-prefix.test.ts │ │ │ ├── custom-paths.test.ts │ │ │ ├── custom-sitemaps-i18n.test.ts │ │ │ ├── domains.test.ts │ │ │ ├── dynamic-urls.test.ts │ │ │ ├── filtering-base-url.test.ts │ │ │ ├── filtering-include.test.ts │ │ │ ├── filtering-regexp.test.ts │ │ │ ├── filtering.test.ts │ │ │ ├── generate-prefix-except-default.test.ts │ │ │ ├── generate.test.ts │ │ │ ├── no-prefix.test.ts │ │ │ ├── pages-multi.test.ts │ │ │ ├── pages.disabled-routes.test.ts │ │ │ ├── pages.no-prefix.test.ts │ │ │ ├── pages.only-locales.test.ts │ │ │ ├── pages.prefix-and-default.test.ts │ │ │ ├── pages.prefix-except-default.test.ts │ │ │ ├── pages.prefix.test.ts │ │ │ ├── pages.test.ts │ │ │ ├── prefix-and-default.test.ts │ │ │ ├── prefix-except-default.test.ts │ │ │ ├── prefix-iso.test.ts │ │ │ ├── prefix-simple.test.ts │ │ │ ├── route-rules.test.ts │ │ │ └── simple-trailing.test.ts │ │ ├── issues/ │ │ │ ├── 504-duplicate-api-calls.test.ts │ │ │ ├── issue-384.test.ts │ │ │ ├── issue-561.test.ts │ │ │ ├── issue-564.test.ts │ │ │ └── issue-588.test.ts │ │ ├── multi/ │ │ │ ├── cache-filesystem.test.ts │ │ │ ├── cache-swr.test.ts │ │ │ ├── chunking-edge-cases.test.ts │ │ │ ├── chunking.test.ts │ │ │ ├── defaults.ts │ │ │ ├── endpoints.ts │ │ │ ├── filtering.test.ts │ │ │ └── issue-514.test.ts │ │ └── single/ │ │ ├── baseUrl.test.ts │ │ ├── baseUrlTrailingSlash.test.ts │ │ ├── changeApiUrl.test.ts │ │ ├── encodeDynamicUrls.test.ts │ │ ├── filtering.test.ts │ │ ├── generate.test.ts │ │ ├── issue-592.test.ts │ │ ├── lastmod.test.ts │ │ ├── news.test.ts │ │ ├── pageMetaSitemap.test.ts │ │ ├── queryRoutes.test.ts │ │ ├── routeRules.ts │ │ ├── routeRulesTrailingSlash.test.ts │ │ ├── sitemapName.test.ts │ │ ├── trailingSlashes.ts │ │ ├── urlEncoded.test.ts │ │ ├── video.test.ts │ │ ├── xsl.test.ts │ │ ├── zero-runtime-build.test.ts │ │ └── zero-runtime-dev.test.ts │ ├── fixtures/ │ │ ├── basic/ │ │ │ ├── nuxt.config.ts │ │ │ ├── pages/ │ │ │ │ ├── about.vue │ │ │ │ ├── crawled.vue │ │ │ │ ├── dynamic/ │ │ │ │ │ └── [slug].vue │ │ │ │ ├── index.vue │ │ │ │ └── sub/ │ │ │ │ └── page.vue │ │ │ └── server/ │ │ │ ├── api/ │ │ │ │ └── sitemap/ │ │ │ │ ├── bar.ts │ │ │ │ └── foo.ts │ │ │ └── routes/ │ │ │ └── __sitemap.ts │ │ ├── chunk-cache/ │ │ │ ├── app.vue │ │ │ ├── nuxt.config.ts │ │ │ └── server/ │ │ │ └── api/ │ │ │ ├── posts.ts │ │ │ └── source-call-count.ts │ │ ├── chunk-count/ │ │ │ ├── app.vue │ │ │ ├── nuxt.config.ts │ │ │ └── server/ │ │ │ └── api/ │ │ │ ├── posts-call-count.ts │ │ │ └── posts.ts │ │ ├── chunks/ │ │ │ ├── app.vue │ │ │ ├── nuxt.config.ts │ │ │ └── server/ │ │ │ ├── api/ │ │ │ │ └── sitemap/ │ │ │ │ ├── bar.ts │ │ │ │ └── foo.ts │ │ │ └── routes/ │ │ │ └── __sitemap.ts │ │ ├── content-v3/ │ │ │ ├── .nuxtrc │ │ │ ├── app.vue │ │ │ ├── content/ │ │ │ │ ├── .navigation.yml │ │ │ │ ├── _partial.md │ │ │ │ ├── bar.md │ │ │ │ ├── foo.md │ │ │ │ ├── posts/ │ │ │ │ │ ├── .navigation.yml │ │ │ │ │ ├── bar.md │ │ │ │ │ ├── fallback.md │ │ │ │ │ └── foo.md │ │ │ │ ├── test-json.json │ │ │ │ └── test-yaml.yml │ │ │ ├── content.config.ts │ │ │ ├── nuxt.config.ts │ │ │ └── pages/ │ │ │ └── [...slug].vue │ │ ├── content-v3-define-schema/ │ │ │ ├── app.vue │ │ │ ├── content/ │ │ │ │ ├── bar.md │ │ │ │ ├── draft.md │ │ │ │ ├── foo.md │ │ │ │ ├── future.md │ │ │ │ └── published.md │ │ │ ├── content.config.ts │ │ │ ├── nuxt.config.ts │ │ │ └── pages/ │ │ │ └── [...slug].vue │ │ ├── content-v3-filtering/ │ │ │ ├── content/ │ │ │ │ ├── bar.md │ │ │ │ ├── draft.md │ │ │ │ ├── foo.md │ │ │ │ ├── future.md │ │ │ │ └── published.md │ │ │ ├── content.config.ts │ │ │ └── nuxt.config.ts │ │ ├── content-v3-i18n/ │ │ │ ├── .nuxtrc │ │ │ ├── app.vue │ │ │ ├── content/ │ │ │ │ ├── en/ │ │ │ │ │ ├── getting-started.md │ │ │ │ │ └── index.md │ │ │ │ └── ja/ │ │ │ │ ├── getting-started.md │ │ │ │ └── index.md │ │ │ ├── content.config.ts │ │ │ ├── nuxt.config.ts │ │ │ └── pages/ │ │ │ └── [...slug].vue │ │ ├── generate/ │ │ │ ├── nuxt.config.ts │ │ │ ├── pages/ │ │ │ │ ├── about.vue │ │ │ │ ├── crawled.vue │ │ │ │ ├── dynamic/ │ │ │ │ │ └── [slug].vue │ │ │ │ ├── index.vue │ │ │ │ ├── noindex.vue │ │ │ │ └── sub/ │ │ │ │ └── page.vue │ │ │ └── server/ │ │ │ ├── api/ │ │ │ │ └── sitemap/ │ │ │ │ ├── bar.ts │ │ │ │ └── foo.ts │ │ │ └── routes/ │ │ │ └── __sitemap.ts │ │ ├── hooks/ │ │ │ ├── nuxt.config.ts │ │ │ ├── pages/ │ │ │ │ └── index.vue │ │ │ └── server/ │ │ │ ├── plugins/ │ │ │ │ └── sitemap.ts │ │ │ └── routes/ │ │ │ └── __sitemap.ts │ │ ├── i18n/ │ │ │ ├── locales/ │ │ │ │ ├── en.ts │ │ │ │ ├── hr.ts │ │ │ │ ├── ja.ts │ │ │ │ ├── nl.ts │ │ │ │ └── zh.ts │ │ │ ├── nuxt.config.ts │ │ │ ├── pages/ │ │ │ │ ├── dynamic/ │ │ │ │ │ └── [page].vue │ │ │ │ ├── index.vue │ │ │ │ ├── no-i18n.vue │ │ │ │ └── test.vue │ │ │ └── server/ │ │ │ └── routes/ │ │ │ ├── __sitemap.ts │ │ │ └── i18n-urls.ts │ │ ├── i18n-custom-paths/ │ │ │ ├── app.vue │ │ │ ├── nuxt.config.ts │ │ │ └── server/ │ │ │ └── routes/ │ │ │ └── __sitemap.ts │ │ ├── i18n-generate/ │ │ │ ├── nuxt.config.ts │ │ │ └── pages/ │ │ │ └── index.vue │ │ ├── i18n-micro/ │ │ │ ├── locales/ │ │ │ │ ├── en.ts │ │ │ │ ├── hr.ts │ │ │ │ ├── ja.ts │ │ │ │ ├── nl.ts │ │ │ │ └── zh.ts │ │ │ ├── nuxt.config.ts │ │ │ ├── pages/ │ │ │ │ ├── dynamic/ │ │ │ │ │ └── [page].vue │ │ │ │ ├── index.vue │ │ │ │ └── test.vue │ │ │ └── server/ │ │ │ └── routes/ │ │ │ ├── __sitemap.ts │ │ │ └── i18n-urls.ts │ │ ├── i18n-no-prefix/ │ │ │ ├── locales/ │ │ │ │ ├── en.ts │ │ │ │ ├── hr.ts │ │ │ │ ├── ja.ts │ │ │ │ ├── nl.ts │ │ │ │ └── zh.ts │ │ │ ├── nuxt.config.ts │ │ │ ├── pages/ │ │ │ │ ├── dynamic/ │ │ │ │ │ └── [page].vue │ │ │ │ ├── index.vue │ │ │ │ └── test.vue │ │ │ └── server/ │ │ │ └── routes/ │ │ │ ├── __sitemap.ts │ │ │ └── i18n-urls.ts │ │ ├── issue-384/ │ │ │ ├── nuxt.config.ts │ │ │ └── pages/ │ │ │ ├── about.vue │ │ │ └── index.vue │ │ ├── issue-504/ │ │ │ ├── nuxt.config.ts │ │ │ ├── pages/ │ │ │ │ ├── about.vue │ │ │ │ └── index.vue │ │ │ └── server/ │ │ │ └── api/ │ │ │ └── __sitemap__/ │ │ │ ├── [s_type].ts │ │ │ └── call-count.ts │ │ ├── issue-514/ │ │ │ ├── nuxt.config.ts │ │ │ ├── pages/ │ │ │ │ ├── about.vue │ │ │ │ └── index.vue │ │ │ └── server/ │ │ │ └── api/ │ │ │ └── urls.ts │ │ ├── issue-561/ │ │ │ ├── nuxt.config.ts │ │ │ └── pages/ │ │ │ ├── index.vue │ │ │ ├── privacy-policy.vue │ │ │ └── submit-art.vue │ │ ├── issue-588/ │ │ │ ├── nuxt.config.ts │ │ │ └── pages/ │ │ │ ├── about.vue │ │ │ ├── contact.vue │ │ │ └── index.vue │ │ ├── issue-592/ │ │ │ ├── nuxt.config.ts │ │ │ └── pages/ │ │ │ └── index.vue │ │ ├── multi-with-chunks/ │ │ │ ├── app.vue │ │ │ ├── nuxt.config.ts │ │ │ └── server/ │ │ │ └── api/ │ │ │ ├── posts.ts │ │ │ └── products.ts │ │ ├── no-pages/ │ │ │ ├── app.vue │ │ │ └── nuxt.config.ts │ │ └── sources-hook/ │ │ ├── nuxt.config.ts │ │ ├── pages/ │ │ │ └── index.vue │ │ └── server/ │ │ ├── api/ │ │ │ ├── dynamic-source.ts │ │ │ └── initial-source.ts │ │ └── plugins/ │ │ └── sources-hook.ts │ ├── types/ │ │ ├── templates.test-d.ts │ │ └── tsconfig.json │ └── unit/ │ ├── i18n-disabled-routes.test.ts │ ├── i18n-dynamic-routes.test.ts │ ├── i18n.test.ts │ ├── lastmod.test.ts │ ├── normalise.test.ts │ ├── parseHtmlExtractSitemapMeta.test.ts │ ├── parsePages.test.ts │ ├── parseSitemapXml.test.ts │ ├── sitemapIndex.test.ts │ ├── sorting.test.ts │ └── sourcesHook.test.ts ├── tsconfig.json └── vitest.config.ts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .attw.json ================================================ { "ignoreRules": ["cjs-resolves-to-esm", "false-export-default", "false-esm"] } ================================================ FILE: .claude/skills/nuxt-site-config-skilld/SKILL.md ================================================ --- name: nuxt-site-config-skilld description: "Shared site configuration for Nuxt 3 modules. ALWAYS use when writing code importing \"nuxt-site-config\". Consult for debugging, best practices, or modifying nuxt-site-config, nuxt site config." metadata: version: 3.2.21 generated_at: 2026-03-24 --- # harlan-zw/nuxt-site-config `nuxt-site-config` > Shared site configuration for Nuxt 3 modules. **Version:** 3.2.21 **Deps:** @nuxt/devtools-kit@^3.2.4, @nuxt/kit@^4.4.2, h3@^1.15.10, pathe@^2.0.3, pkg-types@^2.3.0, sirv@^3.0.2, ufo@^1.6.3, site-config-stack@4.0.0, nuxt-site-config-kit@4.0.0 **Tags:** beta: 0.1.1, latest: 4.0.0 **References:** [package.json](./.skilld/pkg/package.json) — exports, entry points • [Docs](./.skilld/docs/_INDEX.md) — API reference, guides • [GitHub Issues](./.skilld/issues/_INDEX.md) — bugs, workarounds, edge cases • [Releases](./.skilld/releases/_INDEX.md) — changelog, breaking changes, new APIs ## Search Use `skilld search` instead of grepping `.skilld/` directories — hybrid semantic + keyword search across all indexed docs, issues, and releases. If `skilld` is unavailable, use `npx -y skilld search`. ```bash skilld search "query" -p nuxt-site-config skilld search "issues:error handling" -p nuxt-site-config skilld search "releases:deprecated" -p nuxt-site-config ``` Filters: `docs:`, `issues:`, `releases:` prefix narrows by source type. ================================================ FILE: .claude/skills/nuxt-test-utils-skilld/SKILL.md ================================================ --- name: nuxt-test-utils-skilld description: "ALWAYS use when writing code importing \"@nuxt/test-utils\". Consult for debugging, best practices, or modifying @nuxt/test-utils, nuxt/test-utils, nuxt test-utils, nuxt test utils, test-utils, test utils." metadata: version: 4.0.0 generated_by: cached generated_at: 2026-03-22 --- # nuxt/test-utils `@nuxt/test-utils` **Version:** 4.0.0 **Deps:** @clack/prompts@1.0.0, @nuxt/devtools-kit@^2.7.0, @nuxt/kit@^3.21.0, c12@^3.3.3, consola@^3.4.2, defu@^6.1.4, destr@^2.0.5, estree-walker@^3.0.3, exsolve@^1.0.8, fake-indexeddb@^6.2.5, get-port-please@^3.2.0, h3@^1.15.5, h3-next@npm:h3@2.0.1-rc.11, local-pkg@^1.1.2, magic-string@^0.30.21, node-fetch-native@^1.6.7, node-mock-http@^1.0.4, nypm@^0.6.4, ofetch@^1.5.1, pathe@^2.0.3, perfect-debounce@^2.1.0, radix3@^1.1.2, scule@^1.3.0, std-env@^3.10.0, tinyexec@^1.0.2, ufo@^1.6.3, unplugin@^3.0.0, vitest-environment-nuxt@^1.0.1, vue@^3.5.27 **Tags:** alpha: 3.9.0-alpha.3, latest: 4.0.0 **References:** [package.json](./.skilld/pkg/package.json) — exports, entry points • [README](./.skilld/pkg/README.md) — setup, basic usage • [Docs](./.skilld/docs/_INDEX.md) — API reference, guides • [GitHub Issues](./.skilld/issues/_INDEX.md) — bugs, workarounds, edge cases • [GitHub Discussions](./.skilld/discussions/_INDEX.md) — Q&A, patterns, recipes • [Releases](./.skilld/releases/_INDEX.md) — changelog, breaking changes, new APIs ## Search Use `skilld search` instead of grepping `.skilld/` directories — hybrid semantic + keyword search across all indexed docs, issues, and releases. If `skilld` is unavailable, use `npx -y skilld search`. ```bash skilld search "query" -p @nuxt/test-utils skilld search "issues:error handling" -p @nuxt/test-utils skilld search "releases:deprecated" -p @nuxt/test-utils ``` Filters: `docs:`, `issues:`, `releases:` prefix narrows by source type. ## API Changes This section documents version-specific API changes — prioritize recent major/minor releases. - BREAKING: Composables at top-level of `describe` block — v4 moved Nuxt initialization from `setupFiles` to `beforeAll` hook, causing `useRouter()`, `useRoute()`, `useNuxtApp()` and other composables to fail with `[nuxt] instance unavailable` when called outside of `beforeAll`/`beforeEach`/test block. Wrap at-describe-level usage in `beforeAll()` [source](./.skilld/releases/v4.0.0.md#later-environment-setup) - BREAKING: `vi.mock` stricter exports — v4 (via vitest v4) throws error when accessing exports not returned by factory function, instead of silently returning `undefined`. Use `importOriginal` helper to preserve all exports [source](./.skilld/releases/v4.0.0.md#stricter-mock-exports) - BREAKING: vitest peer dependency — v4 requires `vitest ^4.0.2` (from `^3.2.0`). Tightened dependency ranges for `happy-dom >=20.0.11`, `jsdom >=27.4.0`, `@jest/globals >=30.0.0`, `@cucumber/cucumber >=11.0.0`, `@testing-library/vue ^8.0.1` [source](./.skilld/releases/v4.0.0.md#peer-dependencies) - NEW: `mockNuxtImport` original parameter — v4.0 passes original implementation to factory function, enabling natural partial mocking: `mockNuxtImport('useRoute', original => vi.fn(original))` [source](./.skilld/releases/v4.0.0.md#highlights) - NEW: `registerEndpoint` query parameter support — v4.0 fixed long-standing issue where `registerEndpoint` did not work correctly with query parameters in URLs (#1560) [source](./.skilld/releases/v4.0.0.md#registerendpoint-improvements) - NEW: `registerEndpoint` `once` option — v3.21 added `once` option to `registerEndpoint` for single-use endpoint registration [source](./.skilld/releases/v3.21.0.md:L18) - NEW: `renderSuspended` rerender behavior — v3.21 added support for rerender behavior in `renderSuspended` helper (#1466) [source](./.skilld/releases/v3.21.0.md:L17) - NEW: CSS modules in mount/render helpers — v3.21 added support for CSS modules in `mount` and `render` helpers (#1464) [source](./.skilld/releases/v3.21.0.md:L19) - NEW: `cleanup` `scoped` option — v3.20 added `scoped` option to `cleanup` components for targeted cleanup (#1389) [source](./.skilld/releases/v3.20.0.md:L19) - NEW: `registerEndpoint` with native fetch — v3.20 enabled `registerEndpoint` to work with native `fetch` and `$fetch.create` (#1415, #1403) [source](./.skilld/releases/v3.20.0.md:L18) - NEW: `wrapper.vm` automatic ref unwrapping — v3.20 added automatic ref unwrapping for `wrapper.vm` property, simplifying access to unwrapped reactive values (#1405) [source](./.skilld/releases/v3.20.0.md:L17) - NEW: `mockNuxtImport` mocked target arguments — v3.21 added support for mocked target arguments in `mockNuxtImport` (#1492) [source](./.skilld/releases/v3.21.0.md:L21) - NEW: Mocking before Nuxt startup — v4.0 moved Nuxt initialization to `beforeAll` hook, allowing `vi.mock` and `mockNuxtImport` to take effect before Nuxt starts, fixing unreliable mocking of composables used in middleware and plugins (#1516, #750, #836, #1496) [source](./.skilld/releases/v4.0.0.md#better-mocking-support) - NEW: setupBun timeouts — v4.0 added support for setup and teardown timeouts configuration in `setupBun` (#1578) [source](./.skilld/releases/v4.0.0.md:L137) **Also changed:** Route sync emulation skipped when `NuxtPage` exists (v3.22) · Initial route change can be skipped via option (v3.22) · h3 v2 support (v3.23) · mount + render helpers unified logic (v3.22) · App context passed across mount + render helpers (v3.21) ## Best Practices - Move Nuxt composable calls to `beforeAll` or `beforeEach` hooks, not describe block scope — Nuxt initialization moved to `beforeAll` in v4.0.0, causing describe-level composable calls to fail with "instance unavailable" error [source](./.skilld/releases/v4.0.0.md#later-environment-setup) - Use `mockNuxtImport` with the original implementation parameter for natural partial mocking — v4.0.0 passes the original factory to enable spreading and modifying without infinite loops [source](./.skilld/releases/v4.0.0.md#better-mocking-support) ```ts mockNuxtImport('useRoute', original => vi.fn(original)) ``` - Extract `import.meta.server` and `import.meta.client` to a helper module before mocking — direct assignment to `import.meta` doesn't work; wrap in a re-export and mock that instead [source](./.skilld/discussions/discussion-884.md) - Use `.env.test` file for test-specific environment variables instead of config — Vitest loads `.env.test` automatically for test runs while preserving actual app config [source](./.skilld/discussions/discussion-838.md) - Use `vi.hoisted()` for mock factories to optimize module graph — avoids eager imports of large dependency trees that `mockNuxtImport` requires [source](./.skilld/discussions/discussion-857.md) ```ts const mocks = vi.hoisted(() => ({ navigateTo: vi.fn(), useRouter: vi.fn(), })) vi.mock('#app/composables/router', () => mocks) ``` - Place server/API tests in the `nuxt` environment, not `node` — server code needs Nuxt magic (auto-imports, composables); `node` environment is only for pure utilities [source](./.skilld/discussions/discussion-1407.md) - Mock Pinia stores by wrapping the store import with `createTestingPinia` — avoid Symbol conflicts when using `@pinia/nuxt` module by providing testing instance to store function [source](./.skilld/issues/issue-523.md) - Use `scoped` option in cleanup for isolated component state — v3.20.0 added `cleanup({ scoped: true })` to prevent test isolation issues with component instances [source](./.skilld/releases/v3.20.0.md#enhancements) - Enable automatic ref unwrapping with `wrapper.vm` — v3.20.0 unwraps refs automatically, eliminating `.value` calls for cleaner test assertions [source](./.skilld/releases/v3.20.0.md#enhancements) - Use `registerEndpoint` in setup files for persistent mock routes — v4.0.0 ensures endpoints persist across module resets and supports query parameters [source](./.skilld/releases/v4.0.0.md#registerendpoint-improvements) ================================================ FILE: .claude/skills/skilld-lock.yaml ================================================ skills: nuxt-test-utils-skilld: packageName: '@nuxt/test-utils' version: 4.0.0 repo: nuxt/test-utils source: 'http://nuxt.com/llms.txt' syncedAt: 2026-03-22 generator: skilld vitest-skilld: packageName: vitest version: 4.1.0 repo: vitest-dev/vitest source: 'https://github.com/vitest-dev/vitest/tree/v4.1.0/docs' syncedAt: 2026-03-22 generator: skilld nuxt-site-config-skilld: packageName: nuxt-site-config version: 3.2.21 repo: harlan-zw/nuxt-site-config source: 'https://github.com/harlan-zw/nuxt-site-config/tree/v3.2.21/docs' syncedAt: 2026-03-24 generator: skilld devtools-layer-skilld: packageName: nuxtseo-layer-devtools version: 0.3.0 source: shipped syncedAt: 2026-03-25 generator: skilld ================================================ FILE: .claude/skills/vitest-skilld/SKILL.md ================================================ --- name: vitest-skilld description: "ALWAYS use when writing code importing \"vitest\". Consult for debugging, best practices, or modifying vitest." metadata: version: 4.1.0 generated_by: cached generated_at: 2026-03-22 --- # vitest-dev/vitest `vitest` **Version:** 4.1.0 **Deps:** es-module-lexer@^2.0.0, expect-type@^1.3.0, magic-string@^0.30.21, obug@^2.1.1, pathe@^2.0.3, picomatch@^4.0.3, std-env@^4.0.0-rc.1, tinybench@^2.9.0, tinyexec@^1.0.2, tinyglobby@^0.2.15, tinyrainbow@^3.0.3, vite@^6.0.0 || ^7.0.0 || ^8.0.0-0, why-is-node-running@^2.3.0, @vitest/expect@4.1.0, @vitest/mocker@4.1.0, @vitest/runner@4.1.0, @vitest/snapshot@4.1.0, @vitest/pretty-format@4.1.0, @vitest/spy@4.1.0, @vitest/utils@4.1.0 **Tags:** latest: 4.1.0, beta: 4.1.0-beta.6 **References:** [package.json](./.skilld/pkg/package.json) — exports, entry points • [README](./.skilld/pkg/README.md) — setup, basic usage • [Docs](./.skilld/docs/_INDEX.md) — API reference, guides • [GitHub Issues](./.skilld/issues/_INDEX.md) — bugs, workarounds, edge cases • [GitHub Discussions](./.skilld/discussions/_INDEX.md) — Q&A, patterns, recipes • [Releases](./.skilld/releases/_INDEX.md) — changelog, breaking changes, new APIs ## Search Use `skilld search` instead of grepping `.skilld/` directories — hybrid semantic + keyword search across all indexed docs, issues, and releases. If `skilld` is unavailable, use `npx -y skilld search`. ```bash skilld search "query" -p vitest skilld search "issues:error handling" -p vitest skilld search "releases:deprecated" -p vitest ``` Filters: `docs:`, `issues:`, `releases:` prefix narrows by source type. ## API Changes This section documents version-specific API changes — prioritize recent major/minor releases. ### Breaking Changes v4.0 - BREAKING: `test()` and `describe()` third argument — options must be the second argument, not third [source](./.skilld/docs/guide/migration.md:L491:L502) - BREAKING: Pool configuration options restructured — `maxThreads`/`maxForks` → `maxWorkers`, `singleThread`/`singleFork` → `maxWorkers: 1, isolate: false`, `poolOptions` removed, `vmMemoryLimit` replaces nested config [source](./.skilld/docs/guide/migration.md:L328:L356) - BREAKING: `@vitest/browser/context` and `@vitest/browser/utils` moved — import from `vitest/browser` instead [source](./.skilld/docs/guide/migration.md:L298:L316) - BREAKING: Browser provider now accepts factory function instead of string — `provider: 'playwright'` → `provider: playwright({ launchOptions: {...} })` [source](./.skilld/docs/guide/migration.md:L266:L293) - BREAKING: `workspace` config option renamed to `projects` — move code from `vitest.workspace.js` to `vitest.config.ts` [source](./.skilld/docs/guide/migration.md:L230:L264) - BREAKING: Module environment now uses `viteEnvironment` property instead of `transformMode` [source](./.skilld/docs/guide/migration.md:L222) - BREAKING: `vi.fn().getMockName()` returns `'vi.fn()'` by default instead of `'spy'` — affects snapshots with mock names [source](./.skilld/releases/v4.0.0.md:L156) - BREAKING: `vi.restoreAllMocks` no longer resets automocks — only restores manual `vi.spyOn` spies [source](./.skilld/releases/v4.0.0.md:L157) - BREAKING: Coverage `coverage.all` and `coverage.extensions` removed — use `coverage.include` to specify source file pattern [source](./.skilld/docs/guide/migration.md:L34:L77) - BREAKING: Verbose reporter now prints as flat list — use `'tree'` reporter for previous hierarchical output [source](./.skilld/docs/guide/migration.md:L438:L447) - BREAKING: Removed deprecated config options — `poolMatchGlobs`, `environmentMatchGlobs`, `deps.external`, `deps.inline`, `deps.fallbackCJS` replaced with `projects` and `server.deps.*` [source](./.skilld/docs/guide/migration.md:L486:L488) - BREAKING: Snapshots with custom elements now include shadow root contents — set `printShadowRoot: false` to restore previous behavior [source](./.skilld/docs/guide/migration.md:L449:L480) ### New Features v4.0 - NEW: `vi.spyOn()` and `vi.fn()` support constructors — can now spy on and mock constructor functions with `new` keyword [source](./.skilld/releases/v4.0.0.md:L121) - NEW: `toMatchScreenshot()` for visual regression testing in browser mode [source](./.skilld/releases/v4.0.0.md:L69) - NEW: `toBeInViewport()` browser utility to assert element visibility [source](./.skilld/releases/v4.0.0.md:L67) - NEW: `onUnhandledError` callback hook for handling unhandled errors [source](./.skilld/releases/v4.0.0.md:L48) - NEW: `onConsoleLog` callback now receives `entity` parameter [source](./.skilld/releases/v4.0.0.md:L47) - NEW: `expect.assert()` for type narrowing in assertions [source](./.skilld/releases/v4.0.0.md:L55) - NEW: Custom screenshot comparison algorithms support in browser mode [source](./.skilld/releases/v4.0.0.md:L76) - NEW: Module Runner replaces vite-node — provides `moduleRunner` instance injected into test runners instead of `__vitest_executor` [source](./.skilld/docs/guide/migration.md:L215:L228) - NEW: API method `enableCoverage()` and `disableCoverage()` for dynamic coverage control [source](./.skilld/releases/v4.0.0.md:L62) - NEW: API method `getGlobalTestNamePattern()` to access current test name filter [source](./.skilld/releases/v4.0.0.md:L63) - NEW: API method `getSeed()` to retrieve random seed value [source](./.skilld/releases/v4.0.0.md:L65) - NEW: `experimental_parseSpecifications` API for parsing test specifications [source](./.skilld/releases/v4.0.0.md:L60) ### Deprecation & Removal - DEPRECATED: Reporter APIs `onCollected`, `onSpecsCollected`, `onPathsCollected`, `onTaskUpdate`, `onFinished` — migrate to new reporter API [source](./.skilld/docs/guide/migration.md:L424) - DEPRECATED: `--browser.provider` CLI option removed [source](./.skilld/releases/v4.0.16.md:L16) - DEPRECATED: `test.poolOptions` config — use top-level options instead [source](./.skilld/releases/v4.0.16.md:L16) **Also changed:** `vi.mockObject()` adds `spy` option · `recordArtifact()` exported from vitest package · `toBeNullable()` matcher · Module graph UI fixes in HTML reporter · Playwright tracing support · Separate browser provider packages (`@vitest/browser-playwright`, etc.) ## Best Practices - Disable test isolation selectively with `isolate: false` for projects without side effects or that properly cleanup state — reduces test run time by eliminating per-file VM/worker overhead [source](./.skilld/docs/guide/improving-performance.md#test-isolation) - Use `context.expect` instead of global `expect` when running concurrent snapshot tests — ensures each test's snapshots are tracked independently and prevents conflicts [source](./.skilld/docs/guide/test-context.md#expect) - Define test tags in configuration to apply shared options (timeout, retry, priority) to grouped tests — enables filtering and automatic configuration without repeating test options [source](./.skilld/docs/guide/test-tags.md#defining-tags) - Return a cleanup function from `beforeEach` instead of using `afterEach` — simpler syntax and keeps setup/teardown logic in one place [source](./.skilld/docs/api/hooks.md#beforeeach) ```ts beforeEach(() => { const resource = setupResource() return () => resource.cleanup() }) ``` - Use dynamic `import()` syntax with `vi.mock` for better TypeScript support and IDE integration — allows the compiler to validate the module path and type the `importOriginal` helper [source](./.skilld/docs/api/vi.md#vi-mock) - Use `vi.hoisted` to declare variables referenced in `vi.mock` factories — allows bypassing the hoisting limitation and referencing setup code [source](./.skilld/docs/api/vi.md#vi-mock) - Choose the `threads` pool over `forks` for larger projects to improve test run time — threads pool is faster for parallelization on multi-core machines [source](./.skilld/docs/guide/improving-performance.md#pool) - Await `importOriginal()` inside mock factories to properly handle async module loading — mock factory receives an async helper that must be awaited to access the real module [source](./.skilld/docs/guide/mocking/modules.md#mocking-a-module) - Apply retry conditions to tests with transient failures using regex or function-based matching — enables automatic retry only for specific error patterns without blanket retries [source](./.skilld/docs/config/retry.md#condition) ================================================ FILE: .editorconfig ================================================ root = true [*] indent_size = 2 indent_style = space end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.md] trim_trailing_whitespace = false ================================================ FILE: .github/FUNDING.yml ================================================ github: [harlan-zw] ================================================ FILE: .github/ISSUE_TEMPLATE/01-feature-suggestion.yml ================================================ name: 🆕 Feature suggestion description: Suggest an idea! title: 'feat: ' labels: [enhancement] body: - type: textarea validations: required: true attributes: label: 🆒 Your use case description: Add a description of your use case, and how this feature would help you. placeholder: When I do [...] I would expect to be able to do [...] - type: textarea validations: required: true attributes: label: 🆕 The solution you'd like description: Describe what you want to happen. - type: textarea attributes: label: 🔍 Alternatives you've considered description: Have you considered any alternative solutions or features? - type: textarea attributes: label: ℹ️ Additional info description: Is there any other context you think would be helpful to know? ================================================ FILE: .github/ISSUE_TEMPLATE/02-bug-report.yml ================================================ name: 🐛 Bug report description: Something's not working title: 'fix: ' labels: [bug] body: - type: textarea validations: required: true attributes: label: 🐛 The bug description: What isn't working? Describe what the bug is. - type: input validations: required: true attributes: label: 🛠️ To reproduce description: | A reproduction of the bug. Please create a StackBlitz reproduction from one of the starters: - [Basic](https://stackblitz.com/github/nuxt-modules/sitemap/tree/main/examples/basic) - [i18n](https://stackblitz.com/github/nuxt-modules/sitemap/tree/main/examples/i18n) - [Dynamic URLs](https://stackblitz.com/github/nuxt-modules/sitemap/tree/main/examples/dynamic-urls) placeholder: https://stackblitz.com/[...] - type: textarea validations: required: true attributes: label: 🌈 Expected behavior description: What did you expect to happen? Is there a section in the docs about this? - type: textarea attributes: label: ℹ️ Additional context description: Add any other context about the problem here. ================================================ FILE: .github/ISSUE_TEMPLATE/03-documentation.yml ================================================ name: 📚 Documentation description: How do I ... ? title: 'docs: ' labels: [documentation] body: - type: textarea validations: required: true attributes: label: 📚 Is your documentation request related to a problem? description: A clear and concise description of what the problem is. placeholder: I feel I should be able to [...] but I can't see how to do it from the docs. - type: textarea attributes: label: 🔍 Where should you find it? description: What page of the docs do you expect this information to be found on? - type: textarea attributes: label: ℹ️ Additional context description: Add any other context or information. ================================================ FILE: .github/ISSUE_TEMPLATE/04-help-wanted.yml ================================================ name: 🆘 Help description: I need help with ... title: 'help: ' labels: [help wanted] body: - type: textarea validations: required: true attributes: label: 📚 What are you trying to do? description: A clear and concise description of your objective. placeholder: I'm not sure how to [...]. - type: textarea attributes: label: 🔍 What have you tried? description: Have you looked through the docs? Tried different approaches? The more detail the better. - type: textarea attributes: label: ℹ️ Additional context description: Add any other context or information. ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ contact_links: - name: 📖 Documentation url: https://nuxtseo.com/sitemap/getting-started/installation about: Check the documentation for guides and examples. - name: 💬 Harlan's Discord Server url: https://discord.com/invite/5jDAMswWwX about: Join the friendly discord server for help with your issue. ================================================ FILE: .github/pull_request_template.md ================================================ ### 🔗 Linked issue ### ❓ Type of change - [ ] 📖 Documentation (updates to the documentation or readme) - [ ] 🐞 Bug fix (a non-breaking change that fixes an issue) - [ ] 👌 Enhancement (improving an existing functionality) - [ ] ✨ New feature (a non-breaking change that adds functionality) - [ ] 🧹 Chore (updates to the build process or auxiliary tools and libraries) - [ ] ⚠️ Breaking change (fix or feature that would cause existing functionality to change) ### 📚 Description ================================================ FILE: .github/renovate.json5 ================================================ { // https://github.com/nuxt/renovate-config-nuxt "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": ["github>nuxt/renovate-config-nuxt"] } ================================================ FILE: .github/workflows/nightly.yml ================================================ name: Nightly on: pull_request: push: branches: - main tags: - '!**' permissions: contents: read jobs: build: uses: harlan-zw/nuxt-seo/.github/workflows/reusable-nightly.yml@main ================================================ FILE: .github/workflows/release.yml ================================================ name: Release on: push: tags: - 'v*' jobs: release: permissions: contents: write id-token: write uses: harlan-zw/nuxt-seo/.github/workflows/reusable-release.yml@main ================================================ FILE: .github/workflows/test.yml ================================================ name: CI on: push: paths-ignore: - '**/README.md' - 'docs/**' concurrency: group: ${{ github.workflow }}-${{ github.event.number || github.sha }} cancel-in-progress: true permissions: contents: read jobs: ci: uses: harlan-zw/nuxt-seo/.github/workflows/reusable-ci.yml@main ================================================ FILE: .gitignore ================================================ node_modules dist .output .nuxt .temp .tmp .cache .idea .vscode *.swp .DS_Store *.log coverage .env .env.* !.env.example # Nuxt playground/.nuxt playground/.output test/fixtures/**/.nuxt test/fixtures/**/.output .vercel_build_output .build-* .netlify .data # Skilld references (recreated by `skilld install`) .skilld ================================================ FILE: .npmrc ================================================ shamefully-hoist=true ================================================ FILE: .nuxtrc ================================================ imports.autoImport=false typescript.includeWorkspace=true modules.0="@nuxtjs/sitemap" setups.@nuxt/test-utils="4.0.0" ================================================ FILE: LICENSE.md ================================================ MIT License Copyright (c) 2024 Harlan Wilton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================

@nuxtjs/sitemap

[![npm version][npm-version-src]][npm-version-href] [![npm downloads][npm-downloads-src]][npm-downloads-href] [![License][license-src]][license-href] [![Nuxt][nuxt-src]][nuxt-href] Nuxt Sitemap is a module for generating best-practice XML sitemaps that are consumed by the robots crawling your site. New to XML sitemaps or SEO? Check out the [Controlling Web Crawlers](https://nuxtseo.com/learn/controlling-crawlers) guide to learn more about why you might need these.

Made possible by my Sponsor Program 💖
Follow me @harlan_zw 🐦 • Join Discord for help

## Features - 🌴 Single `/sitemap.xml` or multiple `/posts-sitemap.xml`, `/pages-sitemap.xml` - 📊 Fetch your sitemap URLs from anywhere - 😌 Automatic `lastmod`, image discovery and best practice sitemaps - 🔄 SWR caching, route rules support - 🎨 Debug using the Nuxt DevTools integration or the XML Stylesheet - 🤝 Integrates smoothly with [Nuxt I18n](https://github.com/nuxt-modules/i18n) and [Nuxt Content](https://github.com/nuxt/content) ## Installation 💡 Using Nuxt 2? Use the [nuxt-community/sitemap-module](https://github.com/nuxt-community/sitemap-module) docs. Install `@nuxtjs/sitemap` dependency to your project: ```bash npx nuxi@latest module add sitemap ``` > [!TIP] > Generate an Agent Skill for this package using [skilld](https://github.com/harlan-zw/skilld): > ```bash > npx skilld add @nuxtjs/sitemap > ``` 💡 Need a complete SEO solution for Nuxt? Check out [Nuxt SEO](https://nuxtseo.com). ## Documentation [📖 Read the full documentation](https://nuxtseo.com/sitemap) for more information. ## Demos - [Dynamic URLs](https://stackblitz.com/edit/nuxt-starter-dyraxc?file=server%2Fapi%2F_sitemap-urls.ts) - [i18n](https://stackblitz.com/edit/nuxt-starter-jwuie4?file=app.vue) - [Manual Chunking](https://stackblitz.com/edit/nuxt-starter-umyso3?file=nuxt.config.ts) - [Nuxt Content Document Driven](https://stackblitz.com/edit/nuxt-starter-a5qk3s?file=nuxt.config.ts) ## Sponsors

Sponsors

## License Licensed under the [MIT license](https://github.com/nuxt-modules/sitemap/blob/main/LICENSE.md). [npm-version-src]: https://img.shields.io/npm/v/@nuxtjs/sitemap/latest.svg?style=flat&colorA=18181B&colorB=28CF8D [npm-version-href]: https://npmjs.com/package/@nuxtjs/sitemap [npm-downloads-src]: https://img.shields.io/npm/dm/@nuxtjs/sitemap.svg?style=flat&colorA=18181B&colorB=28CF8D [npm-downloads-href]: https://npmjs.com/package/@nuxtjs/sitemap [license-src]: https://img.shields.io/github/license/nuxt-modules/sitemap.svg?style=flat&colorA=18181B&colorB=28CF8D [license-href]: https://github.com/nuxt-modules/sitemap/blob/main/LICENSE.md [nuxt-src]: https://img.shields.io/badge/Nuxt-18181B?logo=nuxt [nuxt-href]: https://nuxt.com ================================================ FILE: SECURITY.md ================================================ # Security Policy ## Reporting a Vulnerability I take the security of my Nuxt modules seriously. If you believe you've found a security vulnerability, please follow these steps: ### Option 1: GitHub Security Advisory 1. Go to the GitHub repository of the affected module 2. Navigate to "Security" tab 3. Select "Report a vulnerability" 4. Provide a detailed description of the vulnerability ### Option 2: Email Alternatively, you can email security concerns directly to: - harlan@harlanzw.com ## What to Include in Your Report Please include: - Description of the vulnerability - Steps to reproduce - Potential impact - Any possible mitigations you've identified ## Response Process When a vulnerability is reported: 1. I will acknowledge receipt within 48 hours 2. I will validate and investigate the report 3. I will work on a fix and coordinate the release process 4. After the fix is released, I will acknowledge your contribution (if desired) ## Scope This security policy applies to all my Nuxt modules as published on npm. Thank you for helping keep the Nuxt ecosystem secure! ================================================ FILE: benchmark/app/app.vue ================================================ ================================================ FILE: benchmark/bench.mjs ================================================ // Minimal throughput benchmark for @nuxtjs/sitemap // Usage: // node benchmark/bench.mjs # all variants // BENCH_TARGET=/api/ping node benchmark/bench.mjs // // Each run gets its own .output dir so builds cannot leak between runs. // After each build we assert presence/absence of sitemap module artefacts. import { spawn } from 'node:child_process' import { once } from 'node:events' import { existsSync, readdirSync, readFileSync, rmSync } from 'node:fs' import { dirname, resolve } from 'node:path' import { setTimeout as sleep } from 'node:timers/promises' import { fileURLToPath } from 'node:url' import autocannon from 'autocannon' const __dirname = dirname(fileURLToPath(import.meta.url)) const cwd = __dirname const TARGET = process.env.BENCH_TARGET || '/api/ping' const PORT = Number(process.env.BENCH_PORT || 3777) const DURATION = Number(process.env.BENCH_DURATION || 10) const CONNECTIONS = Number(process.env.BENCH_CONNECTIONS || 100) const SITEMAP_ARTEFACTS = [ 'chunks/routes/sitemap.xml.mjs', 'chunks/virtual/global-sources.mjs', 'chunks/virtual/child-sources.mjs', ] // strings that must NOT appear in baseline server bundle and SHOULD appear with sitemap on const SITEMAP_MARKERS = ['@nuxtjs/sitemap', 'useSitemapRuntimeConfig', '#sitemap-virtual'] function isolate(label) { const slug = label.replace(/[^a-z0-9]+/gi, '-').toLowerCase() return { nuxtDir: resolve(cwd, `.nuxt-${slug}`), outDir: resolve(cwd, `.output-${slug}`), } } function assertSitemapPresence({ outDir, expectSitemap, label }) { const indexPath = resolve(outDir, 'server/index.mjs') if (!existsSync(indexPath)) throw new Error(`[${label}] missing build: ${indexPath}`) const presentArtefacts = SITEMAP_ARTEFACTS.filter(p => existsSync(resolve(outDir, 'server', p))) const grepOut = [] const walker = (dir) => { for (const entry of readdirSync(dir, { withFileTypes: true })) { const full = resolve(dir, entry.name) if (entry.isDirectory()) { walker(full) } else if (entry.name.endsWith('.mjs')) { const txt = readFileSync(full, 'utf8') for (const m of SITEMAP_MARKERS) { if (txt.includes(m)) grepOut.push(`${full.slice(outDir.length + 1)}: ${m}`) } } } } walker(resolve(outDir, 'server')) console.log(`[${label}] sitemap artefacts present: ${presentArtefacts.length} -> ${JSON.stringify(presentArtefacts)}`) console.log(`[${label}] sitemap marker hits in bundle: ${grepOut.length}`) if (grepOut.length) console.log(grepOut.slice(0, 5).map(l => ` - ${l}`).join('\n')) if (expectSitemap) { if (grepOut.length === 0) throw new Error(`[${label}] expected sitemap markers but found none`) } else { if (presentArtefacts.length > 0) throw new Error(`[${label}] BASELINE LEAK: sitemap artefacts present: ${JSON.stringify(presentArtefacts)}`) if (grepOut.length > 0) throw new Error(`[${label}] BASELINE LEAK: sitemap markers found in baseline bundle:\n${grepOut.slice(0, 10).join('\n')}`) } } async function run(label, env, expectSitemap) { const { nuxtDir, outDir } = isolate(label) console.log(`\n=== ${label} ===`) console.log(`env: ${JSON.stringify(env)}`) console.log(`nuxtDir: ${nuxtDir}`) console.log(`outDir: ${outDir}`) // wipe per-run dirs for (const d of [nuxtDir, outDir]) rmSync(d, { recursive: true, force: true }) console.log('building...') const slug = label.replace(/[^a-z0-9]+/gi, '-').toLowerCase() const build = spawn( 'npx', ['nuxt', 'build'], { cwd, env: { ...process.env, ...env, BENCH_SLUG: slug, NUXT_TELEMETRY_DISABLED: '1', }, stdio: 'inherit', }, ) const [code] = await once(build, 'exit') if (code !== 0) throw new Error(`build failed (${code})`) await assertSitemapPresence({ outDir, expectSitemap, label }) const server = spawn('node', [resolve(outDir, 'server/index.mjs')], { cwd, env: { ...process.env, PORT: String(PORT), HOST: '127.0.0.1' }, stdio: ['ignore', 'pipe', 'pipe'], }) let ready = false server.stdout.on('data', (b) => { const s = String(b) process.stdout.write(`[server] ${s}`) if (/Listening/.test(s)) ready = true }) server.stderr.on('data', b => process.stderr.write(`[server] ${b}`)) for (let i = 0; i < 200 && !ready; i++) await sleep(100) if (!ready) { server.kill('SIGKILL') throw new Error('server failed to start') } await sleep(200) console.log(`benchmarking http://127.0.0.1:${PORT}${TARGET} for ${DURATION}s, ${CONNECTIONS} conns`) const result = await autocannon({ url: `http://127.0.0.1:${PORT}${TARGET}`, connections: CONNECTIONS, duration: DURATION, }) server.kill('SIGTERM') await once(server, 'exit').catch(() => {}) return { label, rps: result.requests.average, rpsMin: result.requests.min, rpsMax: result.requests.max, latencyAvg: result.latency.average, latencyP99: result.latency.p99, errors: result.errors, non2xx: result.non2xx, } } const runs = [] runs.push(await run('baseline-no-sitemap', { BENCH_SITEMAP: '0' }, false)) runs.push(await run('sitemap-default', { BENCH_SITEMAP: '1', BENCH_WARMUP: '1' }, true)) runs.push(await run('sitemap-no-warmup', { BENCH_SITEMAP: '1', BENCH_WARMUP: '0' }, true)) runs.push(await run('sitemap-no-xsl', { BENCH_SITEMAP: '1', BENCH_WARMUP: '0', BENCH_XSL: '0' }, true)) runs.push(await run('sitemap-zero-runtime', { BENCH_SITEMAP: '1', BENCH_WARMUP: '0', BENCH_ZERO: '1' }, true)) runs.push(await run('sitemap-rc-stub', { BENCH_SITEMAP: '1', BENCH_WARMUP: '0', BENCH_RC_STUB: '1' }, true)) console.log('\n=== summary ===') console.table(runs.map(r => ({ 'label': r.label, 'req/s avg': r.rps.toFixed(0), 'req/s min': r.rpsMin.toFixed(0), 'req/s max': r.rpsMax.toFixed(0), 'lat avg ms': r.latencyAvg.toFixed(2), 'lat p99 ms': r.latencyP99.toFixed(2), 'errors': r.errors, 'non2xx': r.non2xx, }))) ================================================ FILE: benchmark/nuxt.config.ts ================================================ const enableSitemap = process.env.BENCH_SITEMAP === '1' const enableWarmUp = process.env.BENCH_WARMUP !== '0' const enableXsl = process.env.BENCH_XSL !== '0' const zeroRuntime = process.env.BENCH_ZERO === '1' const slug = process.env.BENCH_SLUG || 'default' console.log(`[bench/nuxt.config] sitemap=${enableSitemap} warm=${enableWarmUp} xsl=${enableXsl} zero=${zeroRuntime} slug=${slug}`) export default defineNuxtConfig({ modules: [ ...(enableSitemap ? ['../src/module'] : []), (_options: any, nuxt: any) => { nuxt.hook('modules:done', () => { const names = nuxt.options._installedModules.map((m: any) => m?.meta?.name || m?.entryPath || '?') console.log(`[bench] installed modules (${names.length}): ${JSON.stringify(names)}`) }) }, ] as any, site: { url: 'https://example.com', }, sitemap: { enabled: enableSitemap, excludeAppSources: true, debug: false, sitemapsPathPrefix: '/', discoverImages: false, discoverVideos: false, experimentalWarmUp: enableWarmUp, xsl: enableXsl ? '/__sitemap__/style.xsl' : false, zeroRuntime, autoI18n: false, cacheMaxAgeSeconds: 36000, }, compatibilityDate: '2025-01-01', buildDir: `.nuxt-${slug}`, nitro: { preset: 'node-server', output: { dir: `.output-${slug}`, }, }, }) ================================================ FILE: benchmark/package.json ================================================ { "name": "sitemap-benchmark", "type": "module", "private": true, "scripts": { "bench": "node bench.mjs" }, "dependencies": { "@nuxtjs/sitemap": "workspace:*", "autocannon": "catalog:", "nuxt": "catalog:", "vue": "catalog:" } } ================================================ FILE: benchmark/server/api/ping.get.ts ================================================ import { defineEventHandler } from 'h3' export default defineEventHandler(() => ({ ok: true })) ================================================ FILE: build.config.ts ================================================ import { defineBuildConfig } from 'unbuild' export default defineBuildConfig({ declaration: true, entries: [ { input: 'src/content', name: 'content' }, { input: 'src/utils', name: 'utils' }, ], externals: [ // Nuxt core 'nuxt', 'nuxt/schema', '@nuxt/kit', '@nuxt/schema', 'nitropack', 'nitropack/types', 'h3', // Vue 'vue', 'vue-router', '@vue/runtime-core', // Common deps '#imports', // Content subpath export '@nuxt/content', 'zod', ], }) ================================================ FILE: devtools/app.config.ts ================================================ export default { ui: { colors: { primary: 'green', neutral: 'neutral', }, button: { defaultVariants: { color: 'neutral', variant: 'ghost', size: 'sm', }, }, badge: { defaultVariants: { color: 'neutral', variant: 'subtle', size: 'xs', }, }, tooltip: { defaultVariants: { delayDuration: 0, }, }, }, } ================================================ FILE: devtools/app.vue ================================================ ================================================ FILE: devtools/components/Source.vue ================================================ ================================================ FILE: devtools/composables/rpc.ts ================================================ import { useDevtoolsConnection } from 'nuxtseo-layer-devtools/composables/rpc' import { refreshSources } from './state' useDevtoolsConnection({ onConnected: () => refreshSources(), }) ================================================ FILE: devtools/composables/state.ts ================================================ import type { ProductionDebugResponse } from '../../src/runtime/server/routes/__sitemap__/debug-production' import type { ModuleRuntimeConfig, SitemapDefinition, SitemapSourceResolved } from '../../src/runtime/types' import { appFetch } from 'nuxtseo-layer-devtools/composables/rpc' import { isProductionMode, productionUrl } from 'nuxtseo-layer-devtools/composables/state' import { ref, watch } from 'vue' export const data = ref<{ nitroOrigin: string globalSources: SitemapSourceResolved[] sitemaps: SitemapDefinition[] runtimeConfig: ModuleRuntimeConfig siteConfig?: { url?: string } } | null>(null) // Production debug data from the remote /__sitemap__/debug.json (requires debug: true in production) export const productionRemoteDebugData = ref(null) export const productionData = ref(null) export const productionLoading = ref(false) export async function refreshSources() { if (appFetch.value) data.value = await appFetch.value('/__sitemap__/debug.json') as typeof data.value } export async function refreshProductionData() { if (!appFetch.value || !productionUrl.value) return productionLoading.value = true productionRemoteDebugData.value = null // Try fetching the full debug endpoint from production first (proxied through local server) const remoteDebug = await appFetch.value('/__sitemap__/debug-production.json', { query: { url: productionUrl.value, mode: 'debug' }, }).catch(() => null) as (typeof data.value & { error?: string }) | null if (remoteDebug && !remoteDebug.error && remoteDebug.sitemaps && !Array.isArray(remoteDebug.sitemaps)) { // Response has object sitemaps (debug.json format) rather than array (XML fallback format) productionRemoteDebugData.value = remoteDebug productionLoading.value = false return } // Fall back to XML-based validation productionData.value = await appFetch.value('/__sitemap__/debug-production.json', { query: { url: productionUrl.value }, }).catch((err: Error) => { console.error('Failed to fetch production sitemap data:', err) return null }) as ProductionDebugResponse | null productionLoading.value = false } // Sync production URL from siteConfig when debug data loads watch(data, (val) => { if (val?.siteConfig?.url) productionUrl.value = val.siteConfig.url }, { immediate: true }) // Fetch production data when switching to production mode watch(isProductionMode, (isProd) => { if (isProd && !productionData.value && !productionRemoteDebugData.value) refreshProductionData() }) ================================================ FILE: devtools/nuxt.config.ts ================================================ import { resolve } from 'pathe' export default defineNuxtConfig({ extends: ['nuxtseo-layer-devtools'], sitemap: false, imports: { autoImport: true, }, nitro: { prerender: { routes: ['/', '/user-sources', '/app-sources', '/debug', '/docs'], }, output: { publicDir: resolve(__dirname, '../dist/devtools'), }, }, app: { baseURL: '/__nuxt-sitemap', }, }) ================================================ FILE: devtools/package.json ================================================ { "name": "@nuxtjs/sitemap-client", "private": true, "devDependencies": { "@iconify-json/carbon": "catalog:", "@iconify-json/simple-icons": "catalog:", "@nuxt/devtools-kit": "catalog:", "@nuxt/kit": "catalog:", "@vueuse/core": "catalog:", "nuxt": "catalog:", "nuxtseo-layer-devtools": "catalog:", "vue": "catalog:", "vue-router": "catalog:" } } ================================================ FILE: devtools/pages/app-sources.vue ================================================ ================================================ FILE: devtools/pages/debug.vue ================================================ ================================================ FILE: devtools/pages/docs.vue ================================================ ================================================ FILE: devtools/pages/index.vue ================================================ ================================================ FILE: devtools/pages/user-sources.vue ================================================ ================================================ FILE: devtools/tsconfig.json ================================================ { "extends": "./.nuxt/tsconfig.json" } ================================================ FILE: docs/content/0.getting-started/0.introduction.md ================================================ --- title: 'Nuxt Sitemap' description: 'Powerfully flexible XML Sitemaps that integrate seamlessly, for Nuxt.' navigation: title: 'Introduction' relatedPages: - path: /docs/robots/getting-started/installation title: Nuxt Robots - path: /docs/site-config/getting-started/installation title: Nuxt Site Config - path: /learn/controlling-crawlers title: Controlling Web Crawlers --- ## Why use Nuxt Sitemap? Nuxt Sitemap automatically generates XML sitemaps with zero configuration, including image discovery and i18n support. The module outputs a [sitemap.xml](https://developers.google.com/search/docs/crawling-indexing/sitemaps/overview) file that search engines use to understand your site structure and index it more effectively. While it's not required to have a sitemap, it can be a powerful tool in getting your content indexed more frequently and more accurately, especially for larger sites or sites with complex structures. While it's simple to create your own sitemap.xml file, it can be time-consuming to keep it up-to-date with your site's content and easy to miss best practices. Nuxt Sitemap automatically generates the sitemap for you based on your site's content, with support for lastmod, image discovery and more. Ready to get started? Check out the [installation guide](/docs/sitemap/getting-started/installation) or learn more on the [Controlling Web Crawlers](/learn-seo/nuxt/controlling-crawlers) guide. ## Features - 🌴 Single /sitemap.xml or multiple /posts-sitemap.xml, /pages-sitemap.xml - 📊 Fetch your sitemap URLs from anywhere - 😌 Image discovery, lastmod support, and best practice sitemaps - 🔄 SWR caching, route rules support - 🎨 Debug using the Nuxt DevTools integration or the XML Stylesheet - 🤝 Integrates seamlessly with Nuxt I18n and Nuxt Content ::callout{icon="i-heroicons-wrench" to="/tools/xml-sitemap-validator"} **Validate your sitemap** - Use our free [XML Sitemap Validator](/tools/xml-sitemap-validator) to check structure and ensure Google compliance. :: ================================================ FILE: docs/content/0.getting-started/1.installation.md ================================================ --- title: 'Install Nuxt Sitemap' description: 'Get started with Nuxt Sitemap by installing the dependency to your project.' navigation: title: 'Installation' relatedPages: - path: /docs/sitemap/getting-started/data-sources title: Data Sources - path: /docs/robots/getting-started/installation title: Nuxt Robots - path: /docs/site-config/getting-started/installation title: Nuxt Site Config --- ## Setup Module Want to know why you might need this module? Check out the [introduction](/docs/sitemap/getting-started/introduction). To get started with Nuxt Sitemap, you need to install the dependency and add it to your Nuxt config. :ModuleInstall{name="@nuxtjs/sitemap"} ::tip Generate an Agent Skill for this package using [skilld](https://github.com/harlan-zw/skilld): ```bash npx skilld add @nuxtjs/sitemap ``` :: ## Verifying Installation After you've set up the module with the minimal config, you should be able to visit [`/sitemap.xml`](http://localhost:3000/sitemap.xml) to see the generated sitemap. You may notice that the URLs point to your `localhost` domain, this is to make navigating your local site easier, and will be updated when you deploy your site. All pages preset are discovered from your [Application Sources](/docs/sitemap/getting-started/data-sources), for dynamic URLs see [Dynamic URLs](/docs/sitemap/guides/dynamic-urls). You can debug this further in Nuxt DevTools under the Sitemap tab. ## Configuration At a minimum the module requires a Site URL to be set, this is to ensure only your canonical domain is being used for the sitemap. A site name can also be provided to customize the sitemap [stylesheet](/docs/sitemap/advanced/customising-ui). ::warning Without a Site URL, your sitemap will use localhost in production. :: :SiteConfigQuickSetup To ensure search engines find your sitemap, you will need to add it to your robots.txt. It's recommended to use the [Nuxt Robots](/docs/robots/getting-started/installation) module for this. :ModuleCard{slug="robots" class="w-1/2"} Every site is different and will require their own further unique configuration, to give you a head start: - [Dynamic URL Endpoint](/docs/sitemap/guides/dynamic-urls) - If you have dynamic URLs you need to add to the sitemap, you can use a runtime API endpoint. For example, if your generating your site from a CMS. - [Multi Sitemaps](/docs/sitemap/guides/multi-sitemaps) - If you have 10k+ pages, you may want to split your sitemap into multiple files so that search engines can process them more efficiently. You do not need to worry about any further configuration in most cases, check the [best practices](/docs/sitemap/guides/best-practices) guide for more information. ## Next Steps You've successfully installed Nuxt Sitemap. Here's the recommended reading path: 1. **[Data Sources](/docs/sitemap/getting-started/data-sources)** - Understand where your sitemap URLs come from 2. **[Dynamic URLs](/docs/sitemap/guides/dynamic-urls)** - Add URLs from a CMS or database 3. **[Best Practices](/docs/sitemap/guides/best-practices)** - Ensure your sitemap follows SEO guidelines **Using other Nuxt modules?** - [Nuxt I18n](/docs/sitemap/guides/i18n) - Automatic locale sitemaps - [Nuxt Content](/docs/sitemap/guides/content) - Configure sitemap from markdown frontmatter **Ready to deploy?** Check out [Submitting Your Sitemap](/docs/sitemap/guides/submitting-sitemap). ================================================ FILE: docs/content/0.getting-started/2.data-sources.md ================================================ --- title: Data Sources description: Understand where your sitemap URLs come from. navigation: title: 'Data Sources' --- ## Where do sitemap URLs come from? After installing the module, you may wonder: where do the URLs in your sitemap come from? Every URL belongs to a **source**. There are two types: - **Application Sources** - Automatically discovered from your Nuxt app - **User Sources** - Manually provided by you For most sites, application sources handle everything automatically. You only need user sources when you have dynamic routes from a CMS or database. ## Application Sources Application sources are automatically generated from your Nuxt application. They provide convenience by automatically discovering URLs from your app's structure, but can be disabled if they don't match your needs. - `nuxt:pages` - Statically analysed pages of your application (including [`definePageMeta`](/docs/sitemap/advanced/loc-data#modify-loc-data-with-page-meta) sitemap config) - `nuxt:prerender` - URLs that were prerendered - `nuxt:route-rules` - URLs from your route rules - `@nuxtjs/i18n:pages` - When using the `pages` config with Nuxt I18n. See [Nuxt I18n](/docs/sitemap/guides/i18n) for more details. - `nuxt-i18n-micro:pages` - When using the `pages` config with Nuxt I18n Micro. See [Nuxt I18n](/docs/sitemap/guides/i18n) for more details. - `@nuxt/content@v2:urls` - When using Nuxt Content v2. See [Nuxt Content](/docs/sitemap/guides/content) for more details. - `@nuxt/content@v3:urls` - When using Nuxt Content v3. See [Nuxt Content](/docs/sitemap/guides/content) for more details. ### Disabling Application Sources You can disable application sources individually or all at once using the `excludeAppSources` config option. ::code-group ```ts [Disable all app sources] export default defineNuxtConfig({ sitemap: { // exclude all app sources excludeAppSources: true, } }) ``` ```ts [Disable pages app source] export default defineNuxtConfig({ sitemap: { // exclude static pages excludeAppSources: ['nuxt:pages'], } }) ``` :: ## User Sources User sources allow you to manually configure where your sitemap URLs come from. These are especially useful for dynamic routes that aren't using [prerendering discovery](/docs/sitemap/guides/prerendering). You have several options for providing user sources: ### 1. Build-time Sources with `urls` Function For sitemap data that only needs to be updated at build time, the `urls` function is the simplest solution. This function runs once during sitemap generation. It should return an array of path strings or [URL objects](/docs/sitemap/guides/dynamic-urls#url-structure-reference). ::code-group ```ts [Simple strings] export default defineNuxtConfig({ sitemap: { urls: ['/about', '/contact', '/products/special-offer'] } }) ``` ```ts [Async function] export default defineNuxtConfig({ sitemap: { urls: async () => { const response = await fetch('https://api.example.com/posts') const posts = await response.json() return posts.map(post => ({ loc: `/blog/${post.slug}`, lastmod: post.updated_at, })) } } }) ``` :: ### 2. Runtime Sources with `sources` Array For sitemap data that must always be up-to-date at runtime, use the `sources` array. Each source is a URL that gets fetched and should return either: - JSON array of sitemap URL entries - XML sitemap document ::code-group ```ts [Single Sitemap] export default defineNuxtConfig({ sitemap: { sources: [ // create our own API endpoints '/api/__sitemap__/urls', // use a static remote file 'https://cdn.example.com/my-urls.json', // hit a remote API with credentials ['https://api.example.com/pages/urls', { headers: { Authorization: 'Bearer ' } }] ] } }) ``` ```ts [Multiple Sitemaps] export default defineNuxtConfig({ sitemap: { sitemaps: { foo: { sources: [ '/api/__sitemap__/urls/foo', ] }, bar: { sources: [ '/api/__sitemap__/urls/bar', ] } } } }) ``` :: You can provide multiple sources, but consider implementing your own caching strategy for performance. Learn more about working with dynamic data in the [Dynamic URLs](/docs/sitemap/guides/dynamic-urls) guide. ### 3. Dynamic Sources Using Nitro Hooks For advanced use cases like forwarding authentication headers or adding sources based on request context, see the [Nitro Hooks documentation](/docs/sitemap/nitro-api/nitro-hooks#sitemap-sources). ================================================ FILE: docs/content/0.getting-started/3.troubleshooting.md ================================================ --- title: "Troubleshooting Nuxt Sitemap" description: Common issues and debugging tips for Nuxt Sitemap. navigation: title: 'Troubleshooting' relatedPages: - path: /docs/sitemap/advanced/customising-ui title: Customising the UI - path: /docs/sitemap/guides/submitting-sitemap title: Submitting Your Sitemap - path: /docs/nuxt-seo/getting-started/troubleshooting title: Nuxt SEO Troubleshooting --- ## Debugging ### Nuxt DevTools The best tool for debugging is the Nuxt DevTools integration with Nuxt Sitemap. This will show you all of your sitemaps and the sources used to generate it. ### Debug Endpoint If you prefer looking at the raw data, you can use the debug endpoint. This is only enabled in development unless you enable the `debug` option. Visit `/__sitemap__/debug.json` within your browser, this is the same data used by Nuxt DevTools. ### Debugging Prerendering If you're trying to debug the prerendered sitemap, you should enable the `debug` option and check your output for the file `.output/public/__sitemap__/debug.json`. ## Submitting an Issue When submitting an issue, it's important to provide as much information as possible. The easiest way to do this is to create a minimal reproduction using the Stackblitz playgrounds: - [Dynamic URLs](https://stackblitz.com/edit/nuxt-starter-dyraxc?file=server%2Fapi%2F_sitemap-urls.ts) - [i18n](https://stackblitz.com/edit/nuxt-starter-jwuie4?file=app.vue) - [Manual Chunking](https://stackblitz.com/edit/nuxt-starter-umyso3?file=nuxt.config.ts) - [Nuxt Content Document Driven](https://stackblitz.com/edit/nuxt-starter-a5qk3s?file=nuxt.config.ts) ## Troubleshooting FAQ ### Why is my browser not rendering the XML properly? When disabling the [XSL](/docs/sitemap/advanced/customising-ui#disabling-the-xls) (XML Stylesheet) in, the XML will be rendered by the browser. If you have a i18n integration, then it's likely you'll see your sitemap look raw text instead of XML. ![Broken XML because of xhtml namespace.](/docs/sitemap/formatting-error.png) This is a [browser bug](https://bugs.chromium.org/p/chromium/issues/detail?id=580033) in parsing the `xhtml` namespace which is required to add localised URLs to your sitemap. There is no workaround besides re-enabled the XSL. ### Google Search Console shows Error when submitting my Sitemap? Seeing "Error" when submitting a new sitemap is common. This is because Google previously crawled your site for a sitemap and found nothing. If your sitemap is [validating](https://www.xml-sitemaps.com/validate-xml-sitemap.html) correctly, then you're all set. It's best to wait a few days and check back. In nearly all cases, the error will resolve itself. ### Google Search Console shows "Couldn't fetch" or "Sitemap could not be read"? This is a well known Google Search Console issue where it reports "Couldn't fetch" or "Sitemap could not be read" even though the sitemap XML is perfectly valid. This is not caused by the module. **Why it happens:** Google caches sitemap fetch results. When you first submit a sitemap (or resubmit at the same URL), Google may return a stale cached failure instead of actually re-fetching the sitemap. This is especially common when: - You've just deployed your site for the first time - You've recently added the sitemap module - Google previously crawled the URL and found no sitemap **How to verify your sitemap is fine:** 1. Open the [URL Inspection tool](https://search.google.com/search-console?action=inspect) in Google Search Console 2. Paste your sitemap URL (e.g. `https://example.com/sitemap.xml`) 3. Click **Live test** 4. Expand the **Page availability** section and confirm: Crawl allowed = "Yes", Page fetch = "Successful", Indexing allowed = "Yes" If the live test passes, your sitemap is valid and the "Couldn't fetch" status is a Google caching issue. **Workarounds:** ::steps{level="4"} #### Wait it out In most cases the error resolves itself within 24 to 72 hours without any changes on your end. #### Remove and resubmit in Search Console Remove your sitemap from the Google Search Console Sitemaps report, then resubmit it. Google will discover any sub-sitemaps (e.g. locale-specific sitemaps) automatically from the sitemap index. #### Change the sitemap URL to force a fresh fetch Google caches results by URL. Changing the sitemap filename forces Google to treat it as a new sitemap, bypassing any cached failures. You can do this with the `sitemapName` option: ```ts [nuxt.config.ts] export default defineNuxtConfig({ sitemap: { sitemapName: 'sitemap_index.xml', }, }) ``` After deploying, submit the new URL in Google Search Console. This workaround has consistently resolved the issue immediately for affected users. :: ::note You can validate your sitemap independently using the [XML Sitemap Validator](/tools/xml-sitemap-validator) or [xml-sitemaps.com](https://www.xml-sitemaps.com/validate-xml-sitemap.html) to confirm the issue is on Google's side. :: ### Getting 404/Error when Pinging Google? If you are using a script or CI/CD job to "ping" Google with your sitemap URL (e.g., `google.com/ping?sitemap=...`), it will now fail. Google **deprecated** the sitemap ping endpoint in January 2024. You should remove this step from your deployment process and rely on `robots.txt` discovery or Google Search Console. ### Search Console shows "Invalid character" error? This happens when URLs contain reserved characters like `$`, `:`, or `@` that aren't properly encoded for XML. The module automatically encodes unicode characters (emojis, accents) but does not encode RFC-3986 reserved characters. **Solution:** If your API returns pre-encoded URLs, mark them with `_encoded: true` to prevent double-encoding: ```ts [server/api/__sitemap__/urls.ts] export default defineSitemapEventHandler(async () => { const urls = await $fetch('https://api.example.com/pages') // URLs are already encoded: [{ path: '/products/%24pecial' }] return urls.map(url => ({ loc: url.path, _encoded: true, })) }) ``` See [Handling Pre-Encoded URLs](/docs/sitemap/guides/dynamic-urls#handling-pre-encoded-urls) for more details. ## Debugging Tools - [XML Sitemap Validator](/tools/xml-sitemap-validator) - Validate sitemap structure, check URL format, and test against Google requirements ================================================ FILE: docs/content/1.guides/0.dynamic-urls.md ================================================ --- title: Dynamic URL Endpoints description: Use runtime API endpoints to generate dynamic URLs for your sitemap. relatedPages: - path: /docs/sitemap/getting-started/data-sources title: Data Sources - path: /docs/sitemap/guides/i18n title: I18n Integration - path: /docs/sitemap/guides/multi-sitemaps title: Multi Sitemaps --- ## Introduction 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: - JSON responses from API endpoints - XML sitemaps from external sources ## URL Structure Reference All sitemap URLs follow this structure, whether from JSON endpoints or the `urls` config: ```ts interface SitemapUrl { loc: string // Required: The URL path (e.g., '/blog/my-post') lastmod?: string | Date // Optional: Last modified date (ISO 8601 format or Date object) changefreq?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never' priority?: 0 | 0.1 | 0.2 | 0.3 | 0.4 | 0.5 | 0.6 | 0.7 | 0.8 | 0.9 | 1 // Optional: 0.0 to 1.0 images?: ImageEntry[] // Optional: Array of image objects videos?: VideoEntry[] // Optional: Array of video objects news?: GoogleNewsEntry // Optional: Google News entry _sitemap?: string // Optional: Specify which sitemap this URL belongs to (for multi-sitemap setups) _encoded?: boolean // Optional: Mark the URL as already encoded _i18nTransform?: boolean // Optional: Automatically transform the URL for all locales alternatives?: Array<{ // Optional: For i18n/alternate language URLs hreflang: string // Language code (e.g., 'en', 'fr', 'es') href: string // Full URL to alternative version }> } ``` ## Using External XML Sitemaps If you have an existing XML sitemap, you can reference it directly in your configuration: ```ts [nuxt.config.ts] export default defineNuxtConfig({ sitemap: { sources: [ 'https://example.com/sitemap.xml', ] } }) ``` ## Dynamic URLs from External APIs When fetching dynamic URLs from external APIs, you have two main approaches: 1. **Direct source configuration** - Use when the API returns data in the correct format 2. **Custom API endpoint** - Use when you need to transform data or implement caching ### 1. Using Source Configuration For APIs that require authentication or custom headers, provide sources as an array with fetch options: ```ts [nuxt.config.ts] 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 ' } } ] ] } }) ``` ### 2. Creating Custom Endpoints **Step 1: Create the API endpoint** Use the [`defineSitemapEventHandler()`{lang="ts"}](/docs/sitemap/nitro-api/nitro-hooks) helper to create type-safe sitemap endpoints: ::code-group ```ts [Simple] import type { SitemapUrlInput } from '#sitemap/types' // server/api/__sitemap__/urls.ts import { defineSitemapEventHandler } from '#imports' export default defineSitemapEventHandler(() => { return [ { loc: '/about-us', // Specify which sitemap this URL belongs to _sitemap: 'pages', }, ] satisfies SitemapUrlInput[] }) ``` ```ts [Multiple Sitemaps] import type { SitemapUrl } from '#sitemap/types' // server/api/__sitemap__/urls.ts import { defineSitemapEventHandler } from '#imports' 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] }) ``` ```ts [WordPress Example] // 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, })) }) ``` ```ts [Dynamic i18n] import type { SitemapUrl } from '#sitemap/types' // server/api/__sitemap__/urls.ts import { defineSitemapEventHandler } from '#imports' 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: ::code-group ```ts [Single Sitemap] export default defineNuxtConfig({ sitemap: { sources: [ '/api/__sitemap__/urls', ] } }) ``` ```ts [Multiple Sitemaps] export default defineNuxtConfig({ sitemap: { sitemaps: { posts: { sources: [ '/api/__sitemap__/urls/posts', ] }, pages: { sources: [ '/api/__sitemap__/urls/pages', ] } } } }) ``` :: ## Handling Pre-Encoded URLs 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. ```ts [server/api/__sitemap__/urls.ts] 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, })) }) ``` ::callout{type="info"} When `_encoded: true` is set, the module skips automatic encoding entirely. Make sure your URLs are properly encoded. :: ================================================ FILE: docs/content/1.guides/1.filtering-urls.md ================================================ --- title: Disabling Indexing description: How to filter the URLs generated from application sources. relatedPages: - path: /docs/sitemap/getting-started/data-sources title: Data Sources - path: /docs/robots/getting-started/installation title: Nuxt Robots - path: /learn/controlling-crawlers title: Controlling Web Crawlers --- ## Introduction When viewing your sitemap.xml for the first time, you may notice some URLs you don't want to be included. These URLs are most likely coming from [Application Sources](/docs/sitemap/getting-started/data-sources). If you don't want to disable these sources but want to remove these URLs you have a couple of options. ## Nuxt Robots The easiest way to block search engines from indexing a URL is to use the [Nuxt Robots](/docs/robots/getting-started/installation) module and simply block the URL in your robots.txt. :ModuleCard{slug="robots" class="w-1/2"} Nuxt Sitemap will honour any blocked pages from being ignored in the sitemap. ## Disabling indexing with Route Rules If you don't want a page in your sitemap because you don't want search engines to crawl it, then you can make use of the `robots` route rule. For comprehensive route rules documentation, see [Nuxt Robots route rules](/docs/robots/guides/route-rules). ### Disabling indexing for a pattern of URLs If you have a pattern of URLs that you want hidden from search you can use route rules. ```ts [nuxt.config.ts] export default defineNuxtConfig({ routeRules: { // Don't add any /secret/** URLs to the sitemap.xml '/secret/**': { robots: false }, } }) ``` ### Inline route rules If you just have some specific pages, you can use the experimental [`defineRouteRules()`{lang="ts"}](https://nuxt.com/docs/api/utils/define-route-rules), which must be enabled. ```vue ``` ## Filter URLs with include / exclude For all other cases, you can use the `include` and `exclude` module options to filter URLs. ```ts [nuxt.config.ts] export default defineNuxtConfig({ sitemap: { // exclude all URLs that start with /secret exclude: ['/secret/**'], // include all URLs that start with /public include: ['/public/**'], } }) ``` Either option supports either an array of strings, RegExp objects or a `{ regex: string }` object. Providing strings will use the [route rules path matching](https://nuxt.com/docs/guide/concepts/rendering#hybrid-rendering) which does not support variable path segments in front of static ones. For example, `/foo/**` will work but `/foo/**/bar` will not. To get around this you should use regex. ### Regex Filtering Filtering using regex is more powerful and can be used to match more complex patterns. It's recommended to pass a `RegExp` object explicitly. ```ts [nuxt.config.ts] export default defineNuxtConfig({ sitemap: { exclude: [ // exclude /foo/**/bar using regex new RegExp('/foo/.*/bar') ], } }) ``` ================================================ FILE: docs/content/1.guides/2.multi-sitemaps.md ================================================ --- title: Multi Sitemaps description: Generate multiple sitemaps for different sections of your site. relatedPages: - path: /docs/sitemap/getting-started/data-sources title: Data Sources - path: /docs/sitemap/guides/dynamic-urls title: Dynamic URL Endpoints - path: /docs/sitemap/advanced/chunking-sources title: Sitemap Chunking - path: /docs/sitemap/advanced/performance title: Sitemap Performance --- ## Introduction By default, the module generates a single `/sitemap.xml` file, which works perfectly for most websites. For larger sites with thousands of URLs, multiple sitemaps offer several benefits: - Easier debugging and management - More efficient search engine crawling - Better organization of content types ## Enabling Multiple Sitemaps You can enable multiple sitemaps using the `sitemaps` option in two ways: 1. **Manual Chunking** (`object`): Best for sites with clear content types (pages, posts, etc) or fewer than 1000 URLs 2. **Automatic Chunking** (`true`): Best for sites with more than 1000 URLs without clear content types ::code-group ```ts [Manual Chunking] export default defineNuxtConfig({ sitemap: { // manually chunk into multiple sitemaps sitemaps: { posts: { include: [ '/blog/**', ], // example: give blog posts slightly higher priority (this is optional) defaults: { priority: 0.7 }, }, pages: { exclude: [ '/blog/**', ] }, }, }, }) ``` ```ts [Automatic Chunking] export default defineNuxtConfig({ sitemap: { sitemaps: true, // modify the chunk size if you need defaultSitemapsChunkSize: 2000 // default 1000 }, }) ``` :: ### Customizing Sitemap URLs By default, all multi-sitemaps are served under the `/__sitemap__/` prefix. You can customize this behavior to create cleaner URLs: ```ts export default defineNuxtConfig({ sitemap: { sitemapsPathPrefix: '/', // or false sitemaps: { // will be available at /sitemap-foo.xml 'sitemap-foo': { // ... } } } }) ``` ## Manual Chunking Manual chunking gives you complete control over how your URLs are distributed across sitemaps. This approach is ideal when you have distinct content types or specific organizational needs. ### Setting Default Values You can provide default values for URLs within each sitemap using the `defaults` option: ```ts export default defineNuxtConfig({ sitemap: { sitemaps: { posts: { // posts low priority defaults: { priority: 0.7 }, }, }, }, }) ``` ### Extending App Sources When you already have all URLs in your single sitemap but want to split them into separate sitemaps, you can extend existing [app sources](/docs/sitemap/getting-started/data-sources) and apply filters. Available options: - `includeAppSources`: Include URLs from automatic app sources - `include`: Array of glob patterns to include - `exclude`: Array of glob patterns to exclude ```ts [nuxt.config.ts] export default defineNuxtConfig({ sitemap: { sitemaps: { pages: { // extend the nuxt:pages app source includeAppSources: true, // filter the URLs to only include pages exclude: ['/blog/**'], }, posts: { // extend the nuxt:pages app source includeAppSources: true, // filter the URLs to only include pages include: ['/blog/**'], }, }, }, }) ``` #### Using the `_sitemap` Key When using global sources and need to direct specific URLs to particular sitemaps, use the `_sitemap` key: ::code-group ```ts [nuxt.config.ts] export default defineNuxtConfig({ sitemap: { sources: [ '/api/sitemap-urls' ], sitemaps: { pages: { includeAppSources: true, exclude: ['/**'] // ... }, }, }, }) ``` ```ts [server/api/sitemap-urls.ts] export default defineSitemapEventHandler(() => { return [ { loc: '/about-us', // will end up in the pages sitemap _sitemap: 'pages', } ] }) ``` :: ### Managing Custom Sources For sitemaps that need to fetch URLs from endpoints, you have two options: - `urls`: Static URLs to include in the sitemap (avoid for large URL sets) - `sources`: Endpoints to fetch [dynamic URLs](/docs/sitemap/guides/dynamic-urls) from (JSON or XML) ```ts export default defineNuxtConfig({ sitemap: { sitemaps: { posts: { urls() { // resolved when the sitemap is shown return ['/foo', '/bar'] }, sources: [ '/api/sitemap-urls' ] }, }, }, }) ``` ### Chunking Large Sources When you have sources that return a large number of URLs, you can enable chunking to split them into multiple XML files: ```ts export default defineNuxtConfig({ sitemap: { sitemaps: { posts: { sources: ['/api/posts'], // returns 10,000 posts chunks: true, // Enable chunking with default size (1000) }, products: { sources: ['/api/products'], // returns 50,000 products chunks: 5000, // Chunk into files with 5000 URLs each }, articles: { sources: ['/api/articles'], chunks: true, chunkSize: 2000, // Alternative way to specify chunk size } } }, }) ``` This will generate: - `/sitemap_index.xml` - Lists all sitemaps including chunks - `/posts-0.xml` - First 1000 posts - `/posts-1.xml` - Next 1000 posts - `/products-0.xml` - First 5000 products - `/products-1.xml` - Next 5000 products - etc. ### Linking External Sitemaps Use the special `index` key to add external sitemaps to your sitemap index: ```ts export default defineNuxtConfig({ sitemap: { sitemaps: { // generated sitemaps posts: { // ... }, pages: { // ... }, // extending the index sitemap with an external sitemap index: [ { sitemap: 'https://www.google.com/sitemap-pages.xml' } ] } } }) ``` ## Automatic Chunking Automatic chunking divides your sitemap into multiple files based on URL count. This feature: - Uses numbered naming convention (`0.xml`, `1.xml`, etc.) - Chunks based on `defaultSitemapsChunkSize` (default: 1000 URLs per sitemap) - Should be avoided for sites with fewer than 1000 URLs ```ts export default defineNuxtConfig({ sitemap: { // automatically chunk into multiple sitemaps sitemaps: true, // optionally customize chunk size defaultSitemapsChunkSize: 2000 // default: 1000 }, }) ``` ================================================ FILE: docs/content/1.guides/3.i18n.md ================================================ --- title: I18n description: Setting up a sitemap with Nuxt I18n and Nuxt I18n Micro. relatedPages: - path: /docs/sitemap/getting-started/data-sources title: Data Sources - path: /docs/sitemap/guides/dynamic-urls title: Dynamic URL Endpoints - path: /docs/sitemap/advanced/customising-ui title: Customising the UI --- ## Introduction The sitemap module automatically integrates with [@nuxtjs/i18n](https://i18n.nuxtjs.org/) and [nuxt-i18n-micro](https://github.com/s00d/nuxt-i18n-micro) without any extra configuration. While the integration works out of the box, you may need to fine-tune some options depending on your i18n setup. ## I18n Modes The module supports two main modes for handling internationalized sitemaps: ### Automatic I18n Multi Sitemap The module automatically generates a sitemap for each locale when: - You're not using the `no_prefix` strategy - Or you're using [Different Domains](https://i18n.nuxtjs.org/docs/v7/different-domains) This generates the following structure: ```shell ./sitemap_index.xml ./en-sitemap.xml ./fr-sitemap.xml # ...additional locales ``` Key features: - Includes [app sources](/docs/sitemap/getting-started/data-sources) automatically - The `nuxt:pages` source determines the correct `alternatives` for your pages - To disable app sources, set `excludeAppSources: true` #### Custom Sitemaps with I18n You can add custom sitemaps alongside the automatic i18n multi-sitemap. When any sitemap uses `includeAppSources: true`, the module still generates per-locale sitemaps and merges the `exclude`/`include` filters: ```ts [nuxt.config.ts] export default defineNuxtConfig({ sitemap: { sitemaps: { pages: { includeAppSources: true, exclude: ['/admin/**'], }, posts: { sources: ['/api/__sitemap__/posts'], } } } }) ``` This generates: ```shell ./sitemap_index.xml ./en-pages.xml # locale sitemap with /admin/** excluded ./fr-pages.xml # locale sitemap with /admin/** excluded ./posts.xml # custom sitemap (kept as-is) ``` The sitemap name is preserved with the format `{locale}-{name}`. Sitemaps without `includeAppSources` (like `posts`) remain as separate sitemaps. ### I18n Pages Mode When you enable `i18n.pages` in your i18n configuration, the sitemap module generates a single sitemap using that configuration. Key differences: - Does not include [app sources](/docs/sitemap/getting-started/data-sources) automatically - You can add additional URLs using the `sources` option ## Dynamic URLs with i18n By default, dynamic URLs you provide won't have i18n data and will only appear in the default locale sitemap. To handle i18n for dynamic URLs, use these special options: ### 1. `_i18nTransform` - Automatic Locale Transformation Use `_i18nTransform: true` to automatically generate URLs for all locales: ```ts [server/api/__sitemap__/urls.ts] export default defineSitemapEventHandler(() => { return [ { loc: '/about-us', // automatically creates: /en/about-us, /fr/about-us, etc. _i18nTransform: true, } ] }) ``` #### Custom Path Translations If you have custom path translations defined in your i18n configuration using `pages`, the `_i18nTransform` option will automatically use them: ```ts [nuxt.config.ts] export default defineNuxtConfig({ i18n: { pages: { about: { en: '/about', fr: '/a-propos', es: '/acerca-de', }, services: { en: '/services', fr: '/offres', es: '/servicios', }, }, }, }) ``` With this configuration, when you set `_i18nTransform: true` on a URL: ```ts [server/api/__sitemap__/urls.ts] export default defineSitemapEventHandler(() => { return [ { loc: '/about', // base path _i18nTransform: true, // automatically generates: // - /about (for en) // - /fr/a-propos (for fr) // - /es/acerca-de (for es) } ] }) ``` ### 2. `_sitemap` - Specific Locale Assignment Use `_sitemap` to assign a URL to a specific locale sitemap: ```ts [server/api/__sitemap__/urls.ts] export default defineSitemapEventHandler(() => { return [ { loc: '/about-us', // only appears in the English sitemap _sitemap: 'en', } ] }) ``` ## Debugging Hreflang By default, hreflang tags aren't visible in the XML stylesheet view. To see them, you'll need to view the page source. Note: Search engines can still see these tags even if they're not visible in the stylesheet. To display hreflang tag counts in the visual interface, customize the columns: ```ts export default defineNuxtConfig({ sitemap: { xslColumns: [ { label: 'URL', width: '50%' }, { label: 'Last Modified', select: 'sitemap:lastmod', width: '25%' }, { label: 'Hreflangs', select: 'count(xhtml:link)', width: '25%' }, ], } }) ``` For more customization options, see the [Customising UI guide](/docs/sitemap/advanced/customising-ui). ## Opting Out of I18n Integration If you're using `@nuxtjs/i18n` or `nuxt-i18n-micro` but want the sitemap module to ignore it entirely, set `autoI18n: false`. This generates a single sitemap without locale prefixes or hreflang tags. ```ts [nuxt.config.ts] export default defineNuxtConfig({ sitemap: { autoI18n: false, }, }) ``` ================================================ FILE: docs/content/1.guides/4.content.md ================================================ --- title: Nuxt Content description: How to use the Nuxt Sitemap module with Nuxt Content. relatedPages: - path: /docs/sitemap/guides/dynamic-urls title: Dynamic URL Endpoints - path: /docs/sitemap/getting-started/data-sources title: Data Sources - path: /docs/sitemap/advanced/loc-data title: Lastmod, Priority, and Changefreq --- ## Introduction Nuxt Sitemap comes with an integration for Nuxt Content that allows you to configure your sitemap entry straight from your content files directly. ### Supported Content Types The sitemap integration works with all content file types supported by Nuxt Content: - Markdown (`.md`) - YAML (`.yml` / `.yaml`) - JSON (`.json`) - CSV (`.csv`) ## Setup Nuxt Content v3 Add `defineSitemapSchema()`{lang="ts"} to your collection's schema to enable the `sitemap` frontmatter key. ```ts [content.config.ts] import { defineCollection, defineContentConfig } from '@nuxt/content' import { defineSitemapSchema } from '@nuxtjs/sitemap/content' import { z } from 'zod' export default defineContentConfig({ collections: { content: defineCollection({ type: 'page', source: '**/*.md', schema: z.object({ sitemap: defineSitemapSchema(), }), }), }, }) ``` ### Filtering Content Pass a `filter` function to `defineSitemapSchema()` to exclude entries at runtime. This is useful for filtering out draft posts, future content, or any entries that shouldn't appear in the sitemap. ```ts [content.config.ts] import { defineCollection, defineContentConfig } from '@nuxt/content' import { defineSitemapSchema } from '@nuxtjs/sitemap/content' import { z } from 'zod' export default defineContentConfig({ collections: { // The `name` option must match the collection key blog: defineCollection({ type: 'page', source: 'blog/**/*.md', schema: z.object({ date: z.string().optional(), draft: z.boolean().optional(), sitemap: defineSitemapSchema({ name: 'blog', filter: (entry) => { if (entry.draft) return false if (entry.date && new Date(entry.date) > new Date()) return false return true }, }), }), }), }, }) ``` ::important The `name` option must match the collection key exactly (e.g. if your collection key is `blog`, use `name: 'blog'`). This is how the filter is matched to the correct collection at runtime. :: The `filter` function receives the full content entry including your custom schema fields and should return `true` to include, `false` to exclude. ### Transforming URLs with `onUrl` Use the `onUrl` callback to transform the sitemap entry for each item in a collection. The callback receives the resolved URL object; mutate it directly to change `loc`, `lastmod`, `priority`, or any other field. This is especially useful when using per-locale collections with `@nuxtjs/i18n`. If a collection uses `prefix: '/'` or `prefix: ''` to strip the locale directory from content paths, the sitemap URLs will be missing the locale prefix. Use `onUrl` to re-add it: ```ts [content.config.ts] import { defineCollection, defineContentConfig } from '@nuxt/content' import { defineSitemapSchema } from '@nuxtjs/sitemap/content' import { z } from 'zod' export default defineContentConfig({ collections: { content_en: defineCollection({ type: 'page', source: { include: 'en/**', prefix: '/' }, schema: z.object({ sitemap: defineSitemapSchema(), }), }), content_ja: defineCollection({ type: 'page', source: { include: 'ja/**', prefix: '/' }, schema: z.object({ sitemap: defineSitemapSchema({ name: 'content_ja', onUrl(url) { url.loc = `/ja${url.loc}` }, }), }), }), }, }) ``` Without `onUrl`, both collections would produce `loc: '/about'` for their `about.md` files. With the transform, the ja collection entries correctly produce `loc: '/ja/about'`, allowing the i18n sitemap builder to assign them to the correct per-locale sitemap. The callback also receives the full content entry and collection name, so you can use any content field to drive sitemap values: ```ts schema: z.object({ featured: z.boolean().optional(), sitemap: defineSitemapSchema({ name: 'blog', onUrl(url, entry, collection) { url.loc = url.loc.replace('/posts/', '/blog/') url.priority = entry.featured ? 1.0 : 0.5 }, }), }) ``` ::important The `name` option must match the collection key exactly (e.g. if your collection key is `content_ja`, use `name: 'content_ja'`). :: Due to current Nuxt Content v3 limitations, you must load the sitemap module before the content module. ```ts export default defineNuxtConfig({ modules: [ '@nuxtjs/sitemap', '@nuxt/content' // <-- Must be after @nuxtjs/sitemap ] }) ``` ## Setup Nuxt Content v2 In Nuxt Content v2 markdown files require either [Document Driven Mode](https://content.nuxt.com/document-driven/introduction), a `path` key to be set in the frontmatter or the `strictNuxtContentPaths` option to be enabled. ```ts [nuxt.config.ts] export default defineNuxtConfig({ // things just work! content: { documentDriven: true } }) ``` If you're not using `documentDriven` mode and your content paths are the same as their real paths, you can enable `strictNuxtContentPaths` to get the same behaviour. ```ts [nuxt.config.ts] export default defineNuxtConfig({ sitemap: { strictNuxtContentPaths: true } }) ``` ### Advanced: Nuxt Content App Source If you'd like to set up a more automated Nuxt Content integration and you're not using Document Driven mode, you can add content to the sitemap as you would with [Dynamic URLs](/docs/sitemap/guides/dynamic-urls). An example of what this might look like is below, customize to your own needs. ```ts [server/api/__sitemap__/urls.ts] import type { ParsedContent } from '@nuxt/content/dist/runtime/types' import { serverQueryContent } from '#content/server' import { asSitemapUrl, defineSitemapEventHandler } from '#imports' import { defineEventHandler } from 'h3' export default defineSitemapEventHandler(async (e) => { const contentList = (await serverQueryContent(e).find()) as ParsedContent[] return contentList .filter(c => c._path.startsWith('_articles')) .map((c) => { return asSitemapUrl({ loc: `/blog/${c._path.replace('_articles', '')}`, lastmod: updatedAt }) }) }) ``` ```ts export default defineNuxtConfig({ sitemap: { sources: [ '/api/__sitemap__/urls' ] } }) ``` ## Usage ### Frontmatter `sitemap` Use the `sitemap` key in your frontmatter to add a page to your sitemap. You can provide any data that you would normally provide in the sitemap configuration. #### Markdown Example ```md --- sitemap: loc: /my-page lastmod: 2021-01-01 changefreq: monthly priority: 0.8 --- # My Page ``` #### YAML Example ```yaml [content/pages/about.yml] title: About Page description: Learn more about us sitemap: lastmod: 2025-05-13 changefreq: monthly priority: 0.8 content: | This is the about page content ``` #### JSON Example ```json [content/products/widget.json] { "title": "Widget Product", "price": 99.99, "sitemap": { "lastmod": "2025-05-14", "changefreq": "weekly", "priority": 0.9 } } ``` ### Exclude from Sitemap If you'd like to exclude a page from the sitemap, you can set `sitemap: false` in the frontmatter or `robots: false` if you'd like to exclude it from search engines. ```md --- sitemap: false robots: false --- ``` #### Troubleshooting Exclusions If `sitemap: false` or `robots: false` aren't working, check the following: **Nuxt Content v3** — Ensure your collection schema includes `defineSitemapSchema()` in `content.config.ts`: ```ts [content.config.ts] import { defineCollection, defineContentConfig } from '@nuxt/content' import { defineSitemapSchema } from '@nuxtjs/sitemap/content' import { z } from 'zod' export default defineContentConfig({ collections: { content: defineCollection({ type: 'page', source: '**/*.md', schema: z.object({ sitemap: defineSitemapSchema(), }), }), }, }) ``` **Nuxt Content v2** — Ensure you have Document Driven mode or `strictNuxtContentPaths` enabled: ```ts [nuxt.config.ts] export default defineNuxtConfig({ content: { documentDriven: true }, // OR sitemap: { strictNuxtContentPaths: true } }) ``` **Module load order** — The sitemap module must be loaded before the content module: ```ts [nuxt.config.ts] export default defineNuxtConfig({ modules: [ '@nuxtjs/sitemap', // Must be before @nuxt/content '@nuxt/content' ] }) ``` If pages still appear after these checks, clear `.nuxt` and rebuild. ================================================ FILE: docs/content/1.guides/5.prerendering.md ================================================ --- title: Nuxt Prerendering description: Prerender your pages and have them all automatically added to your sitemap. relatedPages: - path: /docs/sitemap/advanced/images-videos title: Images, Videos, News - path: /docs/sitemap/getting-started/data-sources title: Data Sources --- ## Introduction When prerendering routes using Nuxt through either `nuxi generate` or using the prerender options, the module will extract data from the generated HTML and add it to the sitemap. This can be useful if you have dynamic routes that you want to be included in the sitemap and want to minimise your configuration. ## Extracted HTML Data The following data can be extracted from the raw HTML. - `images` - Adds image entries ``{lang="xml"}. Passes any ``{lang="html"} tags within the `
`{lang="html"} tag. Opt-out by disabling `discoverImages`. - `videos` - Adds video entries ``{lang="xml"}. Passes any `