Showing preview only (948K chars total). Download the full file or copy to clipboard to get everything.
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.
<!-- skilld:api-changes -->
## 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)
<!-- /skilld:api-changes -->
<!-- skilld:best-practices -->
## 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)
<!-- /skilld:best-practices -->
================================================
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.
<!-- skilld:api-changes -->
## 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.)
<!-- /skilld:api-changes -->
<!-- skilld:best-practices -->
## 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)
<!-- /skilld:best-practices -->
================================================
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
================================================
<!---
☝️ PR title should follow conventional commits (https://conventionalcommits.org)
-->
### 🔗 Linked issue
<!-- If it resolves an open issue, please link the issue here. For example "Resolves #123" -->
### ❓ Type of change
<!-- What types of changes does your code introduce? Put an `x` in all the boxes that apply. -->
- [ ] 📖 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
<!-- Describe your changes in detail -->
<!-- Why is this change required? What problem does it solve? -->
================================================
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
================================================
<h1>@nuxtjs/sitemap</h1>
[![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.
<p align="center">
<table>
<tbody>
<td align="center">
<sub>Made possible by my <a href="https://github.com/sponsors/harlan-zw">Sponsor Program 💖</a><br> Follow me <a href="https://twitter.com/harlan_zw">@harlan_zw</a> 🐦 • Join <a href="https://discord.gg/275MBUBvgP">Discord</a> for help</sub><br>
</td>
</tbody>
</table>
</p>
## 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
<p align="center">
<a href="https://raw.githubusercontent.com/harlan-zw/static/main/sponsors.svg">
<img src='https://raw.githubusercontent.com/harlan-zw/static/main/sponsors.svg' alt="Sponsors"/>
</a>
</p>
## License
Licensed under the [MIT license](https://github.com/nuxt-modules/sitemap/blob/main/LICENSE.md).
<!-- Badges -->
[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
================================================
<template>
<div>bench</div>
</template>
================================================
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
================================================
<script setup lang="ts">
import { loadShiki } from 'nuxtseo-layer-devtools/composables/shiki'
import { isProductionMode } from 'nuxtseo-layer-devtools/composables/state'
import { computed, ref, watch } from 'vue'
import { navigateTo, useRoute } from '#imports'
import { data, productionData, productionRemoteDebugData, refreshProductionData, refreshSources } from './composables/state'
import './composables/rpc'
await loadShiki()
const refreshing = ref(false)
async function refresh() {
if (refreshing.value)
return
refreshing.value = true
data.value = null
productionData.value = null
productionRemoteDebugData.value = null
await refreshSources()
if (isProductionMode.value)
await refreshProductionData()
setTimeout(() => {
refreshing.value = false
}, 300)
}
const route = useRoute()
const currentTab = computed(() => {
const path = route.path
if (path.startsWith('/user-sources'))
return 'user-sources'
if (path.startsWith('/app-sources'))
return 'app-sources'
if (path.startsWith('/debug'))
return 'debug'
if (path.startsWith('/docs'))
return 'docs'
return 'sitemaps'
})
const navItems = [
{ value: 'sitemaps', to: '/', icon: 'carbon:load-balancer-application', label: 'Sitemaps', devOnly: false },
{ value: 'user-sources', to: '/user-sources', icon: 'carbon:group-account', label: 'User Sources', devOnly: true },
{ value: 'app-sources', to: '/app-sources', icon: 'carbon:bot', label: 'App Sources', devOnly: true },
{ value: 'debug', to: '/debug', icon: 'carbon:debug', label: 'Debug', devOnly: true },
{ value: 'docs', to: '/docs', icon: 'carbon:book', label: 'Docs', devOnly: false },
]
const runtimeVersion = computed(() => data.value?.runtimeConfig?.version)
// Redirect to home when switching to production mode from a dev-only tab
watch(isProductionMode, (isProd) => {
if (isProd && ['user-sources', 'app-sources', 'debug'].includes(currentTab.value))
return navigateTo('/')
})
</script>
<template>
<DevtoolsLayout
v-model:active-tab="currentTab"
module-name="sitemap"
title="Sitemap"
icon="carbon:load-balancer-application"
:version="runtimeVersion"
:nav-items="navItems"
github-url="https://github.com/nuxt-modules/sitemap"
:loading="!data?.globalSources || refreshing"
@refresh="refresh"
>
<NuxtPage />
</DevtoolsLayout>
</template>
<style>
/* Textarea */
textarea {
background: var(--color-surface-sunken);
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
}
textarea:focus-visible {
border-color: var(--seo-green);
outline: none;
}
/* JSON Editor theme */
:root {
--jse-theme-color: var(--color-surface-elevated) !important;
--jse-text-color-inverse: var(--color-text-muted) !important;
--jse-theme-color-highlight: var(--color-surface-sunken) !important;
--jse-panel-background: var(--color-surface-elevated) !important;
--jse-background-color: var(--jse-panel-background) !important;
--jse-error-color: oklch(65% 0.2 25 / 0.3) !important;
--jse-main-border: none !important;
}
.dark,
.jse-theme-dark {
--jse-panel-background: var(--color-neutral-900) !important;
--jse-theme-color: var(--color-neutral-900) !important;
--jse-text-color-inverse: var(--color-neutral-300) !important;
--jse-main-border: none !important;
}
.jse-main {
min-height: 1em !important;
}
.jse-contents {
border-width: 0 !important;
border-radius: var(--radius-md) !important;
}
</style>
================================================
FILE: devtools/components/Source.vue
================================================
<script setup lang="ts">
import type { SitemapSourceResolved } from '../../src/runtime/types'
import { joinURL } from 'ufo'
import { computed } from 'vue'
import { data } from '../composables/state'
const props = defineProps<{ source: SitemapSourceResolved, showContext?: boolean }>()
const fetchUrl = computed(() => {
const url = typeof props.source.fetch === 'string' ? props.source.fetch : props.source.fetch![0]
if (url.includes('http'))
return url
return joinURL(data.value?.nitroOrigin || 'localhost', url)
})
function normaliseTip(tip: string) {
return tip.replace(/`([^`]+)`/g, '<code>$1</code>')
}
</script>
<template>
<DevtoolsSection :class="source.error ? 'source-error' : ''">
<template #text>
<div class="flex items-center gap-3">
<div
v-if="source.fetch"
class="flex items-center gap-1.5"
>
<UIcon
name="carbon:api-1"
class="text-[var(--color-text-muted)]"
/>
<DevtoolsMetric
v-if="source.timeTakenMs"
:value="source.timeTakenMs"
label="ms"
variant="info"
/>
</div>
<span class="font-semibold">{{ source.context.name }}</span>
<DevtoolsMetric
:value="source.urls?.length || 0"
label="URLs"
:variant="source.error ? 'danger' : !source.urls?.length ? 'warning' : 'success'"
/>
</div>
</template>
<template #description>
<div class="flex items-center gap-3">
<a
v-if="source.fetch"
:href="fetchUrl"
target="_blank"
class="link-external text-sm"
>
{{ source.fetch }}
</a>
<span
v-if="source.context.description"
class="text-xs text-[var(--color-text-muted)]"
>
{{ source.context.description }}
</span>
</div>
</template>
<DevtoolsAlert
v-if="source.error"
variant="error"
>
{{ source.error }}
</DevtoolsAlert>
<template v-else>
<DevtoolsAlert
v-if="source._urlWarnings?.length"
variant="warning"
>
<div>
<div class="text-xs font-semibold mb-1">
{{ source._urlWarnings.length }} URL warning{{ source._urlWarnings.length > 1 ? 's' : '' }}
</div>
<ul class="url-warnings-list">
<li
v-for="(w, i) in source._urlWarnings"
:key="i"
>
<code>{{ w.loc }}</code> — {{ w.message }}
</li>
</ul>
</div>
</DevtoolsAlert>
<DevtoolsSnippet
:code="JSON.stringify(source.urls, null, 2)"
lang="json"
label="URLs"
/>
</template>
<DevtoolsAlert
v-if="source.context.tips?.length"
:variant="!source.urls?.length && !source.error ? 'warning' : 'info'"
>
<div>
<h3 class="text-xs font-semibold mb-1.5 text-[var(--color-text)] uppercase tracking-wide opacity-70">
Hints
</h3>
<ul class="space-y-1">
<li
v-for="(tip, key) in source.context.tips"
:key="key"
class="text-sm text-[var(--color-text-muted)] leading-relaxed"
v-html="normaliseTip(tip)"
/>
</ul>
</div>
</DevtoolsAlert>
</DevtoolsSection>
</template>
<style scoped>
.source-error {
border-color: oklch(55% 0.15 25 / 0.35);
}
.source-error:hover {
border-color: oklch(55% 0.15 25 / 0.5);
}
.url-warnings-list {
list-style: none;
padding: 0;
margin: 0;
font-size: 0.6875rem;
line-height: 1.5;
color: var(--color-text-muted);
}
.url-warnings-list li {
padding: 0.125rem 0;
}
.url-warnings-list code {
font-family: var(--font-mono);
font-size: 0.625rem;
padding: 0.0625rem 0.3125rem;
border-radius: 3px;
background: var(--color-surface-sunken);
color: var(--color-text);
}
</style>
================================================
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<typeof data.value | null>(null)
export const productionData = ref<ProductionDebugResponse | null>(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
================================================
<script setup lang="ts">
import { computed } from 'vue'
import Source from '../components/Source.vue'
import { data } from '../composables/state'
const appSources = computed(() => (data.value?.globalSources || []).filter(s => s.sourceType === 'app'))
</script>
<template>
<div class="space-y-5 animate-fade-up">
<div>
<h2 class="text-lg font-semibold mb-1">
App Sources
</h2>
<p class="text-xs text-[var(--color-text-muted)]">
Automatic global sources generated from your application.
</p>
</div>
<template v-if="appSources.length">
<Source
v-for="(source, key) in appSources"
:key="key"
:source="source"
/>
</template>
<DevtoolsEmptyState
v-else
title="No app sources detected"
description="App sources are automatically discovered from your Nuxt application routes and pages."
icon="carbon:bot"
/>
</div>
</template>
================================================
FILE: devtools/pages/debug.vue
================================================
<script setup lang="ts">
import { data } from '../composables/state'
</script>
<template>
<div class="space-y-5 animate-fade-up">
<DevtoolsSection>
<template #text>
<h3 class="opacity-80 text-base mb-1">
<UIcon name="carbon:settings" class="mr-1" />
Runtime Config
</h3>
</template>
<DevtoolsSnippet
:code="JSON.stringify(data?.runtimeConfig, null, 2)"
lang="json"
label="Runtime Config"
/>
</DevtoolsSection>
</div>
</template>
================================================
FILE: devtools/pages/docs.vue
================================================
<template>
<DevtoolsDocs url="https://nuxtseo.com/sitemap" />
</template>
================================================
FILE: devtools/pages/index.vue
================================================
<script setup lang="ts">
import type { SitemapDefinition, SitemapSourceResolved } from '../../src/runtime/types'
import { isProductionMode, productionUrl } from 'nuxtseo-layer-devtools/composables/state'
import { joinURL } from 'ufo'
import { computed } from 'vue'
import Source from '../components/Source.vue'
import { data, productionData, productionLoading, productionRemoteDebugData, refreshProductionData } from '../composables/state'
const appSourcesExcluded = computed(() => data.value?.runtimeConfig?.excludeAppSources || [])
function resolveSitemapOrigin() {
if (isProductionMode.value && productionUrl.value)
return `${productionUrl.value.replace(/\/$/, '')}/`
return data.value?.nitroOrigin || ''
}
function resolveSitemapPath(sitemapName: string) {
const source = productionRemoteDebugData.value || data.value
if (!source)
return ''
const prefix = source.runtimeConfig?.sitemapsPathPrefix || ''
if (sitemapName === 'sitemap' || sitemapName === 'sitemap.xml')
return '/sitemap.xml'
if (sitemapName === 'index')
return '/sitemap_index.xml'
return joinURL('/', prefix, `${sitemapName}-sitemap.xml`)
}
function resolveSitemapUrl(sitemapName: string) {
return `${resolveSitemapOrigin()}${resolveSitemapPath(sitemapName).replace(/^\//, '')}`
}
function resolveSitemapOptions(definition: SitemapDefinition) {
const options: Record<string, any> = {}
Object.entries(definition).forEach(([key, value]) => {
if (value !== undefined && (!Array.isArray(value) || value.length > 0) && key !== 'includeAppSources')
options[key] = value
})
return options
}
function sitemapOptionsAsKeyValues(definition: SitemapDefinition) {
const options = resolveSitemapOptions(definition)
return Object.entries(options).map(([key, value]) => {
const isObject = typeof value === 'object'
return {
key,
value: isObject ? JSON.stringify(value, null, 2) : value,
mono: true,
code: isObject ? 'json' as const : undefined,
}
})
}
function sitemapPathFromUrl(url: string) {
try {
return new URL(url).pathname
}
catch {
return url
}
}
const hasRemoteDebug = computed(() => !!productionRemoteDebugData.value)
const totalProductionUrls = computed(() =>
productionData.value?.sitemaps.reduce((sum, s) => sum + s.urlCount, 0) ?? 0,
)
const totalProductionWarnings = computed(() =>
productionData.value?.sitemaps.reduce((sum, s) => sum + s.warnings.length, 0) ?? 0,
)
</script>
<template>
<div class="space-y-5 animate-fade-up">
<!-- Production mode -->
<template v-if="isProductionMode">
<div class="flex items-center justify-between">
<div>
<h2 class="text-lg font-semibold mb-1">
Production Sitemaps
</h2>
<p class="text-xs text-[var(--color-text-muted)]">
Fetched from {{ productionUrl }}<template v-if="hasRemoteDebug">
with debug mode enabled
</template>.
</p>
</div>
<UButton
icon="carbon:reset"
size="xs"
variant="ghost"
:loading="productionLoading"
@click="refreshProductionData()"
>
Re-validate
</UButton>
</div>
<DevtoolsLoading v-if="productionLoading && !productionData && !productionRemoteDebugData" />
<!-- Full debug view (production has debug: true) -->
<template v-if="hasRemoteDebug">
<DevtoolsSection
v-for="(sitemap, key) in productionRemoteDebugData!.sitemaps"
:key="key"
>
<template #text>
<div class="flex items-center gap-2">
<span class="font-semibold">{{ sitemap.sitemapName }}</span>
<a
target="_blank"
:href="resolveSitemapUrl(sitemap.sitemapName)"
class="link-external text-xs font-mono text-[var(--color-text-muted)]"
>
{{ resolveSitemapPath(sitemap.sitemapName) }}
</a>
<UIcon
v-if="(sitemap.sources || []).some(s => typeof s !== 'string' && 'error' in s && !!s.error)"
name="carbon:warning"
class="text-red-500"
/>
<UIcon
v-else-if="(sitemap.sources || []).some(s => typeof s !== 'string' && '_urlWarnings' in s && s._urlWarnings?.length)"
name="carbon:warning-alt"
class="text-amber-500"
/>
</div>
</template>
<div class="space-y-5">
<template v-if="sitemap.sitemapName === 'index'">
<DevtoolsAlert variant="info">
Links to your other sitemaps.
<a
href="https://developers.google.com/search/docs/crawling-indexing/sitemaps/large-sitemaps"
target="_blank"
class="link-external"
>
Learn more
</a>
</DevtoolsAlert>
</template>
<template v-else>
<div
v-if="sitemap.sources && sitemap.sources.length"
class="flex gap-4"
>
<div class="w-32 flex-shrink-0">
<div class="font-semibold text-sm">
Sources
</div>
</div>
<div class="flex-grow space-y-2">
<Source
v-for="(source, k) in (sitemap.sources as SitemapSourceResolved[])"
:key="k"
:source="source"
/>
</div>
</div>
<div class="flex gap-4">
<div class="w-32 flex-shrink-0">
<div class="font-semibold text-sm">
Options
</div>
</div>
<div class="flex-grow">
<DevtoolsKeyValue
:items="sitemapOptionsAsKeyValues(sitemap)"
striped
/>
</div>
</div>
</template>
</div>
</DevtoolsSection>
</template>
<!-- XML-based fallback view -->
<template v-else-if="productionData?.error">
<DevtoolsProductionError :error="productionData.error" />
</template>
<template v-else-if="productionData">
<!-- Summary -->
<div class="flex items-center gap-4">
<DevtoolsMetric
:value="productionData.sitemaps.length"
:label="productionData.isIndex ? 'child sitemaps' : 'sitemap'"
variant="info"
/>
<DevtoolsMetric
:value="totalProductionUrls"
label="total URLs"
variant="success"
/>
<DevtoolsMetric
v-if="totalProductionWarnings > 0"
:value="totalProductionWarnings"
label="warnings"
variant="warning"
/>
</div>
<!-- Each production sitemap -->
<DevtoolsSection
v-for="(sitemap, i) in productionData.sitemaps"
:key="i"
>
<template #text>
<div class="flex items-center gap-2">
<span class="font-semibold">{{ sitemapPathFromUrl(sitemap.loc) }}</span>
<DevtoolsMetric
:value="sitemap.urlCount"
label="URLs"
variant="success"
/>
<UIcon
v-if="sitemap.error"
name="carbon:warning"
class="text-red-500"
/>
<UIcon
v-else-if="sitemap.warnings.length"
name="carbon:warning-alt"
class="text-amber-500"
/>
</div>
</template>
<template #description>
<a
:href="sitemap.loc"
target="_blank"
class="link-external text-xs font-mono text-[var(--color-text-muted)]"
>
{{ sitemap.loc }}
</a>
</template>
<div v-if="sitemap.error || sitemap.warnings.length" class="space-y-3">
<DevtoolsAlert
v-if="sitemap.error"
variant="warning"
>
{{ sitemap.error }}
</DevtoolsAlert>
<DevtoolsAlert
v-if="sitemap.warnings.length"
variant="warning"
>
<div>
<div class="text-xs font-semibold mb-1">
{{ sitemap.warnings.length }} validation warning{{ sitemap.warnings.length > 1 ? 's' : '' }}
</div>
<ul class="prod-warnings-list">
<li
v-for="(w, wi) in sitemap.warnings"
:key="wi"
>
<template v-if="w.context?.url">
<code>{{ w.context.url }}</code>:
</template>
{{ w.message }}
</li>
</ul>
</div>
</DevtoolsAlert>
</div>
</DevtoolsSection>
<!-- Hint about debug mode -->
<DevtoolsAlert variant="info">
Want to see full source details and URL validation? Deploy with <code>sitemap: { debug: true }</code> to get the same detailed view as development mode.
</DevtoolsAlert>
</template>
</template>
<!-- Local mode -->
<template v-else>
<div>
<h2 class="text-lg font-semibold mb-1">
Sitemaps
</h2>
<p class="text-xs text-[var(--color-text-muted)]">
The sitemaps generated from your site.
</p>
</div>
<DevtoolsSection
v-for="(sitemap, key) in data?.sitemaps"
:key="key"
>
<template #text>
<div class="flex items-center gap-2">
<span class="font-semibold">{{ sitemap.sitemapName }}</span>
<a
target="_blank"
:href="resolveSitemapUrl(sitemap.sitemapName)"
class="link-external text-xs font-mono text-[var(--color-text-muted)]"
>
{{ resolveSitemapPath(sitemap.sitemapName) }}
</a>
<UIcon
v-if="(sitemap.sources || []).some(s => typeof s !== 'string' && 'error' in s && !!s.error)"
name="carbon:warning"
class="text-red-500"
/>
<UIcon
v-else-if="(sitemap.sources || []).some(s => typeof s !== 'string' && '_urlWarnings' in s && s._urlWarnings?.length)"
name="carbon:warning-alt"
class="text-amber-500"
/>
</div>
</template>
<div class="space-y-5">
<template v-if="sitemap.sitemapName === 'index'">
<DevtoolsAlert variant="info">
Links to your other sitemaps.
<a
href="https://developers.google.com/search/docs/crawling-indexing/sitemaps/large-sitemaps"
target="_blank"
class="link-external"
>
Learn more
</a>
</DevtoolsAlert>
</template>
<template v-else>
<div
v-if="sitemap.sources && sitemap.sources.length"
class="flex gap-4"
>
<div class="w-32 flex-shrink-0">
<div class="font-semibold text-sm">
Sources
</div>
</div>
<div class="flex-grow space-y-2">
<Source
v-for="(source, k) in (sitemap.sources as SitemapSourceResolved[])"
:key="k"
:source="source"
/>
</div>
</div>
<div class="flex gap-4">
<div class="w-32 flex-shrink-0">
<div class="font-semibold text-sm">
App Sources
</div>
</div>
<div class="flex-grow flex items-center gap-3">
<div
v-if="sitemap.includeAppSources && appSourcesExcluded !== true"
class="status-enabled"
>
<UIcon
name="carbon:checkmark"
class="text-sm"
/>
<span>Enabled</span>
</div>
<div
v-else
class="status-disabled"
>
<UIcon
name="carbon:close"
class="text-sm"
/>
<span>Disabled</span>
</div>
<NuxtLink
to="/app-sources"
class="text-xs text-[var(--seo-green)] hover:underline"
>
View details
</NuxtLink>
</div>
</div>
<div class="flex gap-4">
<div class="w-32 flex-shrink-0">
<div class="font-semibold text-sm">
Options
</div>
</div>
<div class="flex-grow">
<DevtoolsKeyValue
:items="sitemapOptionsAsKeyValues(sitemap)"
striped
/>
</div>
</div>
</template>
</div>
</DevtoolsSection>
</template>
</div>
</template>
<style scoped>
.status-enabled {
display: inline-flex;
align-items: center;
gap: 0.375rem;
padding: 0.25rem 0.625rem;
font-size: 0.75rem;
font-weight: 500;
border-radius: var(--radius-sm);
background: oklch(75% 0.15 145 / 0.12);
color: oklch(50% 0.15 145);
}
.dark .status-enabled {
background: oklch(50% 0.15 145 / 0.15);
color: oklch(75% 0.18 145);
}
.status-disabled {
display: inline-flex;
align-items: center;
gap: 0.375rem;
padding: 0.25rem 0.625rem;
font-size: 0.75rem;
font-weight: 500;
border-radius: var(--radius-sm);
background: oklch(65% 0.12 25 / 0.1);
color: oklch(55% 0.15 25);
}
.dark .status-disabled {
background: oklch(45% 0.1 25 / 0.15);
color: oklch(70% 0.12 25);
}
.prod-warnings-list {
list-style: none;
padding: 0;
margin: 0;
font-size: 0.6875rem;
line-height: 1.5;
color: var(--color-text-muted);
}
.prod-warnings-list li {
padding: 0.125rem 0;
}
.prod-warnings-list code {
font-family: var(--font-mono);
font-size: 0.625rem;
padding: 0.0625rem 0.3125rem;
border-radius: 3px;
background: var(--color-surface-sunken);
color: var(--color-text);
}
</style>
================================================
FILE: devtools/pages/user-sources.vue
================================================
<script setup lang="ts">
import { computed } from 'vue'
import Source from '../components/Source.vue'
import { data } from '../composables/state'
const userSources = computed(() => (data.value?.globalSources || []).filter(s => s.sourceType === 'user'))
</script>
<template>
<div class="space-y-5 animate-fade-up">
<div>
<h2 class="text-lg font-semibold mb-1">
User Sources
</h2>
<p class="text-xs text-[var(--color-text-muted)]">
Manually provided global sources provided by you.
</p>
</div>
<template v-if="userSources.length">
<Source
v-for="(source, key) in userSources"
:key="key"
:source="source"
/>
</template>
<DevtoolsEmptyState
v-else
title="No user sources configured"
description="Add custom sources via the sources option in your sitemap config."
icon="carbon:add-alt"
/>
</div>
</template>
================================================
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 <token>' } }]
]
}
})
```
```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.

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 <token>' } }
]
]
}
})
```
### 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
<script setup lang="ts">
defineRouteRules({
robots: false
})
</script>
```
## 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 `<image:image>`{lang="xml"}.
Passes any `<img>`{lang="html"} tags within the `<main>`{lang="html"} tag. Opt-out by disabling `discoverImages`.
- `videos` - Adds video entries `<video:video>`{lang="xml"}.
Passes any `<video>`{lang="html"} tags within the `<main>`{lang="html"} tag. Opt-out by disabling `discoverVideos`.
- `lastmod` - Adds lastmod date `<lastmod>`{lang="xml"}.
Uses the [opengraph](https://ogp.me) `article:modified_time` and `article:published_time` meta tag.
## Enabling Nuxt Prerendering
You will need to use configuration to enable this feature.
```ts
export default defineNuxtConfig({
nitro: {
prerender: {
// enabled by default with nuxt generate, not required
crawlLinks: true,
// add any routes to prerender
routes: ['/']
}
}
})
```
You can also use route rules to enable prerendering for specific routes.
```ts
export default defineNuxtConfig({
routeRules: {
'/': { prerender: true }
}
})
```
### Prerendering the Sitemap on Build
If you're using `nuxi build` and want to prerender the sitemap on build, you can add the sitemap path to the `nitro.prerender.routes` option.
```ts
export default defineNuxtConfig({
nitro: {
prerender: {
routes: ['/sitemap.xml']
}
}
})
```
### Customizing the prerender data
If needed, you can customize the prerender data by using the Nitro hooks.
Here is a simple recipe that will extract YouTube video iframes and add them to the sitemap.
```ts
import type { ResolvedSitemapUrl } from '#sitemap/types'
export default defineNuxtConfig({
modules: [
// run this before the sitemap module
(_, nuxt) => {
nuxt.hooks.hook('nitro:init', async (nitro) => {
nitro.hooks.hook('prerender:generate', async (route) => {
const html = route.contents
// check for youtube video iframes and append to the videos array
const matches = html.match(/<iframe.*?youtube.com\/embed\/(.*?)".*?<\/iframe>/g)
if (matches) {
const sitemap = route._sitemap || {} as ResolvedSitemapUrl
sitemap.videos = sitemap.videos || []
for (const match of matches) {
const videoId = match.match(/youtube.com\/embed\/(.*?)" /)[1]
sitemap.videos.push({
title: 'YouTube Video',
description: 'A video from YouTube',
content_loc: `https://www.youtube.com/watch?v=${videoId}`,
thumbnail_loc: `https://img.youtube.com/vi/${videoId}/0.jpg`,
})
}
// the sitemap module should be able to pick this up
route._sitemap = sitemap
}
})
})
},
],
})
```
================================================
FILE: docs/content/1.guides/6.best-practices.md
================================================
---
title: Sitemap.xml Best Practices
description: The best practices for generating a sitemap.xml file.
navigation:
title: Best Practices
relatedPages:
- path: /docs/sitemap/advanced/loc-data
title: Lastmod, Priority, and Changefreq
- path: /docs/sitemap/guides/submitting-sitemap
title: Submitting Your Sitemap
- path: /learn/controlling-crawlers
title: Controlling Web Crawlers
---
## Set appropriate lastmod
The `lastmod` field is used to indicate when a page was last updated. This is used by search engines to determine how often to crawl your site.
This should not change based on code changes, only for updating the content.
For example, if you have a blog post, the `lastmod` should be updated when the content of the blog post changes.
::callout{icon="i-heroicons-exclamation-triangle" color="amber"}
**Accuracy is Critical**
Google has stated that they may **stop trusting** your `lastmod` dates if they are consistently updated without significant content changes. Ensure your `lastmod` logic is precise.
::
It's recommended not to use `autoLastmod: true` as this will use the last time the page was built, which does
not always reflect content updates.
Learn more in [Google's sitemap lastmod documentation](https://developers.google.com/search/blog/2023/06/sitemaps-lastmod-ping).
## You probably don't need `changefreq` or `priority`
These two fields are not used by search engines, and are only used by crawlers to determine how often to crawl your site.
If you're trying to get your site crawled more often, you should use the `lastmod` field instead.
Learn more in [Google's sitemap best practices](https://developers.google.com/search/blog/2023/06/sitemaps-lastmod-ping).
## Use Zero Runtime when content only changes on deploy
If your pages only change when you commit and deploy (not at runtime), you don't need runtime sitemap generation. Enable `zeroRuntime` to generate sitemaps at build time and remove ~50KB of sitemap code from your server bundle.
```ts
export default defineNuxtConfig({
sitemap: {
zeroRuntime: true
}
})
```
This is ideal for sites using `nuxt build` where content is static between deployments. If you're using a CMS that updates content without redeploying, you'll need runtime generation.
Learn more in the [Zero Runtime](/docs/sitemap/guides/zero-runtime) guide.
::callout{icon="i-heroicons-check-circle" to="/tools/xml-sitemap-validator"}
**Check your sitemap** - Validate your sitemap meets Google requirements with our [XML Sitemap Validator](/tools/xml-sitemap-validator).
::
::checklist{id="sitemap-best-practices" title="Quick Checklist"}
- Set meaningful lastmod dates based on content changes
- Skip changefreq and priority (ignored by search engines)
- Enable zeroRuntime for static sites
- Submit sitemap to Google Search Console
::
================================================
FILE: docs/content/1.guides/7.submitting-sitemap.md
================================================
---
title: 'Submitting Your Sitemap'
description: 'How to submit your sitemap to Google Search Console to start getting indexed.'
relatedPages:
- path: /docs/sitemap/getting-started/troubleshooting
title: Troubleshooting
- path: /docs/sitemap/guides/best-practices
title: Best Practices
- path: /learn/controlling-crawlers
title: Controlling Web Crawlers
---
## Introduction
When going live with a new site and you're looking to get indexed by Google, the best starting point is
to submit your sitemap to Google Search Console.
> Google Search Console is a free service offered by Google that helps you monitor, maintain, and troubleshoot
your site's presence in Google Search results.
::callout{icon="i-heroicons-shield-check" to="/tools/xml-sitemap-validator"}
**Validate before submitting** - Use our [XML Sitemap Validator](/tools/xml-sitemap-validator) to check for errors before submitting to Google Search Console.
::
## Submitting Sitemap
Google provides a guide on [Submitting your Sitemap to Google](https://developers.google.com/search/docs/crawling-indexing/sitemaps/build-sitemap) which is a great starting point.
You should index either `/sitemap.xml` or if you're using multiple sitemaps, add `/sitemap_index.xml`.
### Deprecation of Sitemap Pinging
In January 2024, Google [deprecated the "ping" endpoint](https://developers.google.com/search/blog/2023/06/sitemaps-lastmod-ping) (e.g., `http://www.google.com/ping?sitemap=...`).
You should no longer use this method to notify Google of updates. Instead, rely on:
1. **robots.txt reference**: Ensure your `robots.txt` contains the `Sitemap: ...` line (Nuxt Sitemap does this automatically).
2. **Google Search Console**: Submit the sitemap once, and Google will crawl it periodically.
3. **lastmod**: Keep your `lastmod` dates accurate so Google knows when to recrawl specific URLs.
## Requesting Indexing
It's important to know that submitting your sitemap does not guarantee that all your pages will be indexed and that it may take
some time for Google to crawl and index your pages.
To speed up the process, you can use the [URL Inspection Tool](https://support.google.com/webmasters/answer/9012289) to request indexing of a specific URL.
In some cases you may want to expedite the indexing process, for this, you can try out my free open-source tool [Request Indexing](https://requestindexing.com).
## Sitemap Error
When submitting a sitemap for the first time you may get see "Error". This is because Google previously
crawled your site for a sitemap and found nothing.
When encountering this it's best to wait a few days and see if the error resolves itself. If not, you can
try resubmitting the sitemap or making a [GitHub Issue](https://github.com/nuxt-modules/sitemap).
================================================
FILE: docs/content/1.guides/8.zero-runtime.md
================================================
---
title: Zero Runtime
description: Generate sitemaps at build time without runtime overhead.
---
If your sitemap URLs only change when you deploy, you don't need to ship sitemap generation code to production. The `zeroRuntime` option generates sitemaps at build time and tree-shakes the generation code from your server bundle.
## Usage
To enable zero runtime, add the following to your config:
```ts [nuxt.config.ts]
export default defineNuxtConfig({
sitemap: {
zeroRuntime: true
}
})
```
When enabled, the module will automatically add `/sitemap.xml` to your prerender routes. The sitemap will be generated during build and served as a static file at runtime.
## How it Works
With `zeroRuntime: true`:
1. Sitemap routes are automatically added to `nitro.prerender.routes`
2. Server handlers use dynamic imports gated by `import.meta.prerender`
3. At build time, the sitemap generation code is tree-shaken from the runtime bundle
4. Static XML files are served directly without any sitemap code execution
## Development Mode
Zero runtime mode still works in development (`nuxt dev`). The sitemap generation code runs normally during development so you can test your configuration.
## Benchmarks
Enabling `zeroRuntime` reduces the server bundle by approximately:
- **~50KB** uncompressed
- **~5KB** gzip
This is the sitemap generation code (XML building, URL normalization, source fetching) being tree-shaken from the bundle.
## Limitations
- Runtime sitemap generation is not available - sitemaps are only generated during build
- Dynamic data sources that require runtime fetching won't work
- Debug endpoints are disabled in zero runtime mode
## When to Use
Zero runtime is ideal when:
- Your pages only change when you commit and deploy
- You're using `nuxt generate` for a fully static site
- You want to minimize your server bundle size for edge/serverless
## When Not to Use
Avoid zero runtime when:
- Your CMS updates content without redeploying
- You have user-generated content that changes frequently
- Your sitemap URLs depend on runtime data
================================================
FILE: docs/content/2.advanced/0.loc-data.md
================================================
---
title: Lastmod, Priority, and Changefreq
description: Configure lastmod, priority, and changefreq values for your sitemap entries.
relatedPages:
- path: /docs/sitemap/guides/best-practices
title: Best Practices
- path: /docs/sitemap/guides/dynamic-urls
title: Dynamic URL Endpoints
- path: /docs/sitemap/guides/prerendering
title: Nuxt Prerendering
---
## Introduction
Changing the `<loc>`{lang="xml"} entry data can be useful for a variety of reasons, such as changing the `changefreq`, `priority`, or `lastmod` values.
If you're using [Dynamic URLs](/docs/sitemap/guides/dynamic-urls), you can modify the data in the `sitemap` object, otherwise, you will
need to override the [app sources](/docs/sitemap/getting-started/data-sources) directly.
While modifying these in most cases may be unnecessary, see [Best Practices](/docs/sitemap/guides/best-practices), it can be useful when used right.
## Setting Defaults
While this is not recommended, in special circumstances you may wish to set defaults for your sitemap entries:
```ts [nuxt.config.ts]
export default defineNuxtConfig({
sitemap: {
defaults: {
lastmod: new Date().toISOString(),
priority: 0.5,
changefreq: 'weekly'
}
}
})
```
## Data Source Merging
You can provide the page you want to set the `lastmod`, `priority`, or `changefreq` for in your app sources, which includes
the `urls` config.
```ts [nuxt.config.ts]
export default defineNuxtConfig({
sitemap: {
urls: [
{
loc: '/about',
lastmod: '2023-01-01',
priority: 0.3,
changefreq: 'daily'
}
]
}
})
```
## Modify Loc Data With Route Rules
To change the behaviour of your sitemap URLs, you can use [Route rules](https://nuxt.com/docs/api/configuration/nuxt-config/#routerules).
```ts [nuxt.config.ts]
export default defineNuxtConfig({
routeRules: {
// Don't add any /secret/** URLs to the sitemap.xml
'/secret/**': { robots: false },
// modify the sitemap.xml entry for specific URLs
'/about': { sitemap: { changefreq: 'daily', priority: 0.3 } }
}
})
```
Alternatively, you can use the experimental macro [`defineRouteRules()`{lang="ts"}](https://nuxt.com/docs/api/utils/define-route-rules), which must
be enabled.
```vue [pages/index.vue]
<script setup>
defineRouteRules({
sitemap: {
changefreq: 'daily',
priority: 0.3
}
})
</script>
```
## Modify Loc Data With Page Meta
You can configure sitemap entry data directly in your page components using [`definePageMeta()`{lang="ts"}](https://nuxt.com/docs/api/utils/define-page-meta).
```vue [pages/about.vue]
<script setup>
definePageMeta({
sitemap: {
changefreq: 'daily',
priority: 0.8
}
})
</script>
```
To exclude a page from the sitemap entirely, set `sitemap` to `false`:
```vue [pages/secret.vue]
<script setup>
definePageMeta({
sitemap: false
})
</script>
```
The `sitemap` key is extracted at build time via Nuxt's `scanPageMeta`, so these values are available without runtime overhead.
## Dynamic lastmod from APIs
When fetching dynamic URLs, include lastmod from your data source:
```ts [server/api/__sitemap__/urls.ts]
import { defineSitemapEventHandler } from '#imports'
export default defineSitemapEventHandler(async () => {
const posts = await $fetch('https://api.example.com/posts')
return posts.map(post => ({
loc: `/blog/${post.slug}`,
lastmod: post.updated_at,
}))
})
```
With Nuxt Content, set `lastmod` in frontmatter:
```md
---
sitemap:
lastmod: 2024-01-15
changefreq: weekly
---
```
The `lastmod` field accepts `Date` objects, ISO 8601 strings (`'2024-01-15T10:30:00Z'`), or simple date strings (`'2024-01-15'`). Only include it if you have accurate update timestamps — avoid using current date/time for all URLs.
## Lastmod: Prerendering Hints
When prerendering your site, you can make use of setting the `article:modified_time` meta tag in your page's head. This
meta tag will be used as the `lastmod` value in your sitemap.
```vue [pages/index.vue]
<script setup>
useSeoMeta({
// will be inferred as the lastmod value in the sitemap
articleModifiedTime: '2023-01-01'
})
</script>
```
================================================
FILE: docs/content/2.advanced/1.images-videos.md
================================================
---
title: Images, Videos, News
description: Learn how to add images, videos and news in your sitemap.
relatedPages:
- path: /docs/sitemap/guides/prerendering
title: Nuxt Prerendering
- path: /docs/sitemap/guides/best-practices
title: Best Practices
---
## Introduction
The `image`, `video` and `news` namespaces is added to your sitemap by default, allowing you to configure
images, videos and news for your sitemap entries.
When prerendering your app, it's possible for the generated sitemap to automatically infer images and videos from your pages.
## Sitemap Images
To add images to your sitemap, you can use the `images` property on the sitemap entry.
You can learn more about images in sitemaps on the [Google documentation](https://developers.google.com/search/docs/advanced/sitemaps/image-sitemaps).
```ts
export interface ImageEntry {
loc: string | URL
caption?: string
geo_location?: string
title?: string
license?: string | URL
}
```
You can implement this as follows:
```ts [nuxt.config.ts]
export default defineNuxtConfig({
sitemap: {
urls: [
{
loc: '/blog/my-post',
images: [
{
loc: 'https://example.com/image.jpg',
caption: 'My image caption',
geo_location: 'My image geo location',
title: 'My image title',
license: 'My image license',
}
]
}
]
}
})
```
### Automatic Image Discovery
The module can discover images in your page and add them to your sitemap automatically.
For this to work:
- The page _must_ be prerendered. These images will not be shown in development or if the page is not prerendered.
- You must wrap your page content with a `<main>`{lang="html"} tag, avoid wrapping shared layouts that include duplicate images.
## Videos
To add videos to your sitemap, you can use the `videos` property on the sitemap entry.
::callout{icon="i-heroicons-exclamation-triangle" color="amber"}
**Google Video Indexing Change**
As of late 2023, Google **only indexes videos if they are the main content of the page**. If a video is supplementary to the text (like a blog post with a video at the bottom), the video itself may not be indexed in video search results, though the page will still be indexed normally.
::
The TypeScript interface for videos is as follows:
```ts
export interface VideoEntry {
title: string
thumbnail_loc: string | URL
description: string
content_loc?: string | URL
player_loc?: string | URL
duration?: number
expiration_date?: Date | string
rating?: number
view_count?: number
publication_date?: Date | string
family_friendly?: 'yes' | 'no' | boolean
restriction?: Restriction
platform?: Platform
price?: ({
price?: number | string
currency?: string
type?: 'rent' | 'purchase' | 'package' | 'subscription'
})[]
requires_subscription?: 'yes' | 'no' | boolean
uploader?: {
uploader: string
info?: string | URL
}
live?: 'yes' | 'no' | boolean
tag?: string | string[]
category?: string
gallery_loc?: string | URL
}
```
You can learn more about videos in sitemaps on the [Google documentation](https://developers.google.com/search/docs/advanced/sitemaps/video-sitemaps).
You can implement this as follows:
```ts [nuxt.config.ts]
export default defineNuxtConfig({
sitemap: {
urls: [
{
loc: '/blog/my-post',
videos: [
{
title: 'My video title',
thumbnail_loc: 'https://example.com/video.jpg',
description: 'My video description',
content_loc: 'https://example.com/video.mp4',
player_loc: 'https://example.com/video.mp4',
duration: 600,
expiration_date: '2021-01-01',
rating: 4.2,
view_count: 1000,
publication_date: '2021-01-01',
family_friendly: true,
restriction: {
relationship: 'allow',
restriction: 'US CA',
},
platform: {
relationship: 'allow',
platform: 'web',
},
price: [
{
price: 1.99,
currency: 'USD',
type: 'rent',
}
],
requires_subscription: true,
uploader: {
uploader: 'My video uploader',
info: 'https://example.com/uploader',
},
live: true,
tag: ['tag1', 'tag2'],
}
]
}
]
}
})
```
### Automatic Video Discovery
Like automatic image discovery, you can opt-in to automatic video discovery including video markup in your `<main>`{lang="html"} tag.
You are also required to provide a title and description for your video, this can be done using the `data-title` and `data-description` attributes.
```html [Simple]
<video
controls
poster="https://archive.org/download/DuckAndCover_185/__ia_thumb.jpg"
width="620"
data-title="Duck and Cover"
data-description="This film, a combination of animated cartoon and live action, shows young children what to do in case of an atomic attack."
>
<source
src="https://archive.org/download/DuckAndCover_185/CivilDefenseFilm-DuckAndCoverColdWarNuclearPropaganda_512kb.mp4"
type="video/mp4"
/>
<source
src="https://archive.org/download/DuckAndCover_185/CivilDefenseFilm-DuckAndCoverColdWarNuclearPropaganda.avi"
type="video/x-msvideo"
/>
Sorry, your browser doesn't support embedded videos. However, you can
<a href="https://archive.org/details/DuckAndCover_185">download it</a>
and watch it with your favorite video player!
</video>
```
```html [Full]
<video
controls
poster="https://archive.org/download/DuckAndCover_185/__ia_thumb.jpg"
width="620"
data-title="Duck and Cover"
data-description="This film, a combination of animated cartoon and live action, shows young children what to do in case of an atomic attack."
data-rating="4.2"
data-view-count="1000"
data-publication-date="2021-01-01"
data-family-friendly="yes"
>
```
Each format would be added to your sitemap in the following format:
```xml
<video:video>
<video:thumbnail_loc>https://archive.org/download/DuckAndCover_185/__ia_thumb.jpg</video:thumbnail_loc>
<video:title>Duck and Cover</video:title>
<video:description>
This film, a combination of animated cartoon and live action, shows young children what to do in case of an atomic attack.
</video:description>
<video:content_loc>
https://archive.org/download/DuckAndCover_185/CivilDefenseFilm-DuckAndCoverColdWarNuclearPropaganda_512kb.mp4
</video:content_loc>
</video:video>
```
## News
To add news to your sitemap, you can use the `news` property on the sitemap entry. Only [Google's News sitemap](https://developers.google.com/search/docs/crawling-indexing/sitemaps/news-sitemap) extension is supported.
The TypeScript interface for news is as follows:
```ts
export interface GoogleNewsEntry {
title: string
publication_date: Date | string
publication: {
name: string
language: string
}
}
```
You can implement this as follows:
```ts [nuxt.config.ts]
export default defineNuxtConfig({
sitemap: {
urls: [
{
loc: '/news/nuxt-sitemap-turns-6',
news: {
title: 'Nuxt Sitemap Turns 6',
publication_date: '2021-01-01',
publication: {
name: 'Nuxt Sitemap',
language: 'en',
},
}
}
]
}
})
```
## Image & Video Opt-out
To opt-out of this behaviour, you can set the `discoverImages` and `discoverVideos` config to `false` respectively.
```ts [nuxt.config.ts]
export default defineNuxtConfig({
sitemap: {
discoverImages: false,
discoverVideos: false,
}
})
```
================================================
FILE: docs/content/2.advanced/2.performance.md
================================================
---
title: Sitemap Performance
description: Use the default cache engine to keep your sitemaps fast.
relatedPages:
- path: /docs/sitemap/guides/multi-sitemaps
title: Multi Sitemaps
- path: /docs/sitemap/advanced/chunking-sources
title: Sitemap Chunking
- path: /docs/site-config/getting-started/installation
title: Nuxt Site Config
---
## Introduction
For apps with 100k+ pages, generating a sitemap can be a slow process. As robots will request your sitemap frequently, it's important to keep it fast.
Nuxt SEO provides a default cache engine to keep your sitemaps fast and recommendations on how to improve performance.
## Performance Recommendations
When dealing with many URLs that are being generated from an external API, the best option is use the `sitemaps`
option to create [Named Sitemap Chunks](/docs/sitemap/guides/multi-sitemaps).
Each sitemap should contain its own `sources`, this allows other sitemaps to be generated without waiting for this request.
```ts
export default defineNuxtConfig({
sitemap: {
sitemaps: {
posts: {
sources: [
'https://api.something.com/urls'
]
},
},
},
})
```
If you need to split this up further, you should consider chunking by the type and some pagination format. For example,
you can paginate by when posts were created.
```ts
export default defineNuxtConfig({
sitemap: {
sitemaps: {
posts2020: {
sources: [
'https://api.something.com/urls?filter[yearCreated]=2020'
]
},
posts2021: {
sources: [
'https://api.something.com/urls?filter[yearCreated]=2021'
]
},
},
},
})
```
Additionally, you may want to consider the following experimental options that may help with performance:
- `experimentalCompression` - Gzip's and streams the sitemap
- `experimentalWarmUp` - Creates the sitemaps when Nitro starts
### Very large sites (100k+ URLs)
For sites at this scale, two practices matter most:
1. **Cache the source endpoint.** Use `defineCachedEventHandler` on any `/api/*` route fed into `sources`. Without this, every cache miss (and every fresh chunk) re-hits your backend.
2. **Set generous chunk sizes.** Search engines accept up to 50,000 URLs per file. The default `defaultSitemapsChunkSize` of 1000 generates 50× more chunks than necessary; bumping to `5000`–`50000` directly reduces total work and cache entries.
Within a single sitemap, all chunks share one resolved-URLs computation (sources are fetched, normalised, and sorted once per `cacheMaxAgeSeconds` window — not once per chunk). Splitting one large sitemap into per-shard sitemaps (e.g. one per locale or content type) is still useful when shards have different cache lifetimes or different sources.
## Zero Runtime Mode
If your sitemap URLs only change when you deploy (not at runtime), you can enable `zeroRuntime` to generate sitemaps at build time and eliminate sitemap generation code from your server bundle.
```ts
export default defineNuxtConfig({
sitemap: {
zeroRuntime: true
}
})
```
This reduces server bundle size by ~50KB. The sitemap is generated once at build time and served as a static file.
See the [Zero Runtime](/docs/sitemap/guides/zero-runtime) guide for details.
## Sitemap Caching
Caching your sitemap can help reduce the load on your server and improve performance.
By default, SWR caching is enabled on production environments and sitemaps will be cached for 10 minutes.
This is configured by overriding your route rules and leveraging the native Nuxt caching.
### Cache Time
You can change the cache time by setting the `cacheMaxAgeSeconds` option. This affects the `Cache-Control` header sent to browsers and search engines.
```ts
export default defineNuxtConfig({
sitemap: {
cacheMaxAgeSeconds: 3600 // 1 hour
}
})
```
If you want to disable caching, set `cacheMaxAgeSeconds` to `false` or `0`.
`cacheMaxAgeSeconds` controls both the HTTP `Cache-Control` header and the server-side SWR cache TTL. For high-volume sites, raising it to several hours significantly reduces origin load.
### Cache Driver
The cache engine is set to the Nitro default of the `cache/` path.
If you want to customise the cache engine, you can set the `runtimeCacheStorage` option.
```ts [nuxt.config.ts]
export default defineNuxtConfig({
sitemap: {
// cloudflare kv binding example
runtimeCacheStorage: {
driver: 'cloudflare-kv-binding',
binding: 'OG_IMAGE_CACHE'
}
}
})
```
================================================
FILE: docs/content/2.advanced/3.chunking-sources.md
================================================
---
title: Sitemap Chunking
description: Split large sitemap sources into multiple files for performance and search engine limits.
relatedPages:
- path: /docs/sitemap/guides/multi-sitemaps
title: Multi Sitemaps
- path: /docs/sitemap/advanced/performance
title: Sitemap Performance
- path: /docs/sitemap/getting-started/data-sources
title: Data Sources
---
## Introduction
When dealing with large datasets, sitemap sources can be chunked into multiple files to:
- Stay within search engine limits (50MB file size, 50,000 URLs)
- Improve generation performance
- Better manage memory usage
## Simple Configuration
Enable chunking on any named sitemap with sources:
```ts [nuxt.config.ts]
export default defineNuxtConfig({
sitemap: {
sitemaps: {
posts: {
sources: ['/api/posts'],
chunks: true, // Uses default size of 1000
}
}
}
})
```
This generates:
```
/sitemap_index.xml # Master index
/posts-0.xml # First chunk (1-1000)
/posts-1.xml # Second chunk (1001-2000)
...
```
## Chunk Size Options
Configure chunk sizes using different approaches:
```ts [nuxt.config.ts]
export default defineNuxtConfig({
sitemap: {
// Global default
defaultSitemapsChunkSize: 5000,
sitemaps: {
// Using boolean (applies default)
posts: {
sources: ['/api/posts'],
chunks: true,
},
// Using number as size
products: {
sources: ['/api/products'],
chunks: 10000,
},
// Using explicit chunkSize (highest priority)
articles: {
sources: ['/api/articles'],
chunks: true,
chunkSize: 2000,
}
}
}
})
```
## Skipping the index source fetch (`chunkCount`)
By default the sitemap index calls your source to count URLs, so it knows how many `<sitemap>` entries to emit. At very large scale this cold-start fetch is the bottleneck. If you already know the number of chunks, declare it upfront and the index will skip the fetch entirely:
```ts [nuxt.config.ts]
export default defineNuxtConfig({
sitemap: {
sitemaps: {
posts: {
sources: ['/api/posts'],
chunks: 5000,
chunkCount: 100, // 100 chunk entries, no source fetch in the index
},
},
},
})
```
Per-chunk renders still fetch on demand and slice. If your data set grows past the declared count, tail entries are unreachable; if it shrinks, trailing chunks render empty. Update the value when your data set changes (or remove it to fall back to fetching).
## Practical Examples
### E-commerce Site
```ts [nuxt.config.ts]
export default defineNuxtConfig({
sitemap: {
defaultSitemapsChunkSize: 10000,
sitemaps: {
products: {
sources: ['/api/products/all'],
chunks: 2000,
},
categories: {
sources: ['/api/categories'],
chunks: true, // Uses default 10k
}
}
}
})
```
### Large Content Site
```ts [nuxt.config.ts]
export default defineNuxtConfig({
sitemap: {
sitemaps: {
'blog-posts': {
sources: ['/api/blog/posts'],
chunks: 5000,
},
'authors': {
sources: ['/api/authors'],
chunks: false, // Explicitly disable
}
}
}
})
```
## Source Implementation
Basic endpoint for sitemap sources:
```ts [server/api/products/all.ts]
export default defineEventHandler(async () => {
const products = await db.products.findAll({
select: ['id', 'slug', 'updatedAt']
})
return products.map(product => ({
loc: `/products/${product.slug}`,
lastmod: product.updatedAt
}))
})
```
For large datasets, use caching and streaming:
```ts [server/api/products/all.ts]
export default defineCachedEventHandler(async () => {
const products = []
const cursor = db.products.cursor({
select: ['slug', 'updatedAt']
})
for await (const product of cursor) {
products.push({
loc: `/products/${product.slug}`,
lastmod: product.updatedAt
})
}
return products
}, {
maxAge: 60 * 60, // 1 hour cache
name: 'sitemap-products'
})
```
## Debugging
Check chunk configuration and performance:
```ts [nuxt.config.ts]
export default defineNuxtConfig({
sitemap: {
debug: true,
sitemaps: {
products: {
sources: ['/api/products'],
chunks: 5000
}
}
}
})
```
Visit `/__sitemap__/debug.json` to see chunk details and generation metrics.
================================================
FILE: docs/content/2.advanced/4.customising-ui.md
================================================
---
title: Customising the UI
description: Change the look and feel of your sitemap.
relatedPages:
- path: /docs/sitemap/guides/i18n
title: I18n Integration
- path: /docs/sitemap/api/config
title: Config Reference
---
## Disabling the XSL
What you're looking at when you view the sitemap.xml is a XSL file, think of it just like you would a CSS file for HTML.
To view the real sitemap.xml, you can view the source of the page.
If you prefer, you can disable the XSL by setting `xsl` to `false`.
```ts
export default defineNuxtConfig({
sitemap: {
xsl: false
}
})
```
## Changing the columns
You can change the columns that are displayed in the sitemap by modifying the `xslColumns` option.
These have no effect on SEO and is purely for developer experience.
Note: You must always have a `URL` column at the start.
```ts
export default defineNuxtConfig({
sitemap: {
xslColumns: [
// URL column must always be set, no value needed
{ label: 'URL', width: '75%' },
{ label: 'Last Modified', select: 'sitemap:lastmod', width: '25%' },
],
},
})
```
The `select` you provide is an XSL expression that will be evaluated against the sitemap entry.
It's recommended to prefix the value with `sitemap:` if in doubt.
### Example: Adding priority and changefreq
```ts
export default defineNuxtConfig({
sitemap: {
xslColumns: [
{ label: 'URL', width: '50%' },
{ label: 'Last Modified', select: 'sitemap:lastmod', width: '25%' },
{ label: 'Priority', select: 'sitemap:priority', width: '12.5%' },
{ label: 'Change Frequency', select: 'sitemap:changefreq', width: '12.5%' },
],
},
})
```
### Example: Adding `hreflang`
```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%' },
],
},
})
```
## Disabling tips
In development tips are displayed on the sitemap page to help you get started.
You can disable these tips by setting the `xslTips` option to `false`.
```ts
export default defineNuxtConfig({
sitemap: {
xslTips: false,
},
})
```
================================================
FILE: docs/content/4.api/0.config.md
================================================
---
title: Config
description: Configure the sitemap module.
---
## `enabled`
- Type: `boolean`{lang="ts"}
- Default: `true`{lang="ts"}
Whether to generate the sitemap.
## `sortEntries`
- Type: `boolean`{lang="ts"}
- Default: `true`{lang="ts"}
Whether the sitemap entries should be sorted or be shown in the order they were added.
When enabled the entries will be sorted by the `loc`, they will be sorted by the path segment
count and then alphabetically using `String.localeCompare` to ensure numbers are sorted correctly.
## `sources`
- Type: `SitemapSource[]`{lang="ts"}
- Default: `[]`{lang="ts"}
The sources to use for the sitemap. See [Data Sources](/docs/sitemap/getting-started/data-sources) and [Dynamic URL Endpoint](/docs/sitemap/guides/dynamic-urls) for details.
## `excludeAppSources`
- Type: `true | AppSourceContext[]`{lang="ts"}
- Default: `[]`{lang="ts"}
Whether to exclude [app sources](/docs/sitemap/getting-started/data-sources) from the sitemap.
## `appendSitemaps`
- Type: `(string | { sitemap: string, lastmod?: string })[]`{lang="ts"}
- Default: `undefined`{lang="ts"}
Sitemaps to append to the sitemap index.
This will only do anything when using multiple sitemaps.
## `autoLastmod`
- Type: `boolean`{lang="ts"}
- Default: `false`{lang="ts"}
Sets the current date as the default `lastmod` for all entries that don't already have one.
## `sitemaps`
- Type: `boolean | Record<string, SitemapConfig> & { index?: (string | SitemapIndexEntry)[] }`
- Default: `false`
Whether to generate multiple sitemaps.
Each sitemap can have the following options:
### SitemapConfig
#### `sources`
- Type: `SitemapSource[]`
- Default: `[]`
Data sources for this specific sitemap.
#### `chunks`
- Type: `boolean | number`
- Default: `undefined`
Enable chunking for sitemap sources. This splits large collections of URLs from sources into multiple smaller sitemap files to stay within search engine limits.
- Set to `true` to enable chunking with the default chunk size (from `defaultSitemapsChunkSize` or 1000)
- Set to a positive number to use that as the chunk size (e.g., `5000` for 5000 URLs per chunk)
- Set to `false` or leave undefined to disable chunking
Note: Chunking only applies to URLs from `sources`. Direct URLs in the `urls` property are not chunked.
```ts
export default defineNuxtConfig({
sitemap: {
sitemaps: {
products: {
sources: ['/api/products'],
chunks: 5000 // Split into files with 5000 URLs each
}
}
}
})
```
#### `chunkSize`
- Type: `number`
- Default: `undefined`
Explicitly set the chunk size for this sitemap. Takes precedence over the `chunks` property when both are specified.
```ts
export default defineNuxtConfig({
sitemap: {
sitemaps: {
posts: {
sources: ['/api/posts'],
chunks: true, // Enable chunking
chunkSize: 2500 // Use 2500 URLs per chunk
}
}
}
})
```
See the [Chunking Sources](/docs/sitemap/advanced/chunking-sources) guide for more details.
#### `urls`
- Type: `SitemapUrlInput[] | (() => SitemapUrlInput[] | Promise<SitemapUrlInput[]>)`
- Default: `[]`
URLs to include in this sitemap. Can be strings or sitemap URL objects.
#### `include`
- Type: `(string | RegExp | { regex: string })[]`
- Default: `undefined`
Filter URLs to include in this sitemap.
#### `exclude`
- Type: `(string | RegExp | { regex: string })[]`
- Default: `undefined`
Filter URLs to exclude from this sitemap.
#### `defaults`
- Type: `SitemapItemDefaults`
- Default: `{}`
Default values for all URLs in this sitemap.
#### `includeAppSources`
- Type: `boolean`
- Default: `false`
Whether to include automatic app sources in this sitemap.
See [Multi Sitemaps](/docs/sitemap/guides/multi-sitemaps) for details.
## `defaultSitemapsChunkSize`
- Type: `number | false`
- Default: `1000`
The default chunk size when chunking is enabled for multi-sitemaps. This value is used when:
- A sitemap has `chunks: true` (without specifying a number)
- No `chunkSize` is explicitly set for the sitemap
Set to `false` to disable chunking by default for all sitemaps.
```ts
export default defineNuxtConfig({
sitemap: {
defaultSitemapsChunkSize: 5000,
sitemaps: {
// These will use 5000 as chunk size
posts: {
sources: ['/api/posts'],
chunks: true
},
// This overrides the default
products: {
sources: ['/api/products'],
chunks: 10000
}
}
}
})
```
## `defaults`
- Type: `object`
- Default: `{}`
Default values for the sitemap.xml entries. See [sitemaps.org](https://www.sitemaps.org/protocol.html) for all available options.
## `urls`
- Type: `MaybeFunction<MaybePromise<SitemapUrlInput[]>>`
- Default: `[]`
Provide custom URLs to be included in the sitemap.xml.
## `include`
- Type: `(string | RegExp | { regex: string })[]`
- Default: `[]`
Filter routes that match the given rules. If empty, all routes are included. See the [Filtering URLs](/docs/sitemap/guides/filtering-urls) guide for details.
```ts
export default defineNuxtConfig({
sitemap: {
include: [
'/my-hidden-url'
]
}
})
```
## `exclude`
- Type: `(string | RegExp | { regex: string })[]`
- Default: `['/_**']`
Filter routes that match the given rules. The pattern `/_nuxt/**` is always added dynamically from `app.buildAssetsDir`, and `/__nuxt_content/**` is added when Nuxt Content v3 is detected. See the [Filtering URLs](/docs/sitemap/guides/filtering-urls) guide for details.
```ts
export default defineNuxtConfig({
sitemap: {
exclude: [
'/my-secret-section/**'
]
}
})
```
## `xsl`
- Type: `string | false`
- Default: `/__sitemap__/style.xsl`
The path to the XSL stylesheet for the sitemap.xml. Set to `false` to disable.
## `discoverImages`
- Type: `boolean`
- Default: `true`
Whether to discover images from routes when prerendering.
## `discoverVideos`
- Type: `boolean`
- Default: `true`
Whether to discover videos from routes when prerendering.
## `autoI18n`
- Type: `undefined | boolean | { locales: NormalisedLocales; defaultLocale: string; strategy: 'prefix' | 'prefix_except_default' | 'prefix_and_default' | 'no_prefix'; differentDomains?: boolean; pages?: Record<string, Record<string, string | false>> }`
- Default: `undefined`
Automatically add alternative language prefixes for each entry with the given prefixes. Set to `false` to disable.
When using the @nuxtjs/i18n module, this will automatically be set to the configured `locales` when left `undefined`.
## `sitemapName`
- Type: `string`
- Default: `sitemap.xml`
Modify the name of the root sitemap.
Note: This only works when you're not using the multiple `sitemaps` option.
## `strictNuxtContentPaths`
- Type: `boolean`
- Default: `false`
Whether the paths within nuxt/content match their real paths. This is useful when you're using the `nuxt/content` module
without documentDriven mode.
## `cacheMaxAgeSeconds`
- Type: `number | false`
- Default: `60 * 10`
The time in seconds to cache the sitemaps. Set to `false` to disable caching.
## `sitemapsPathPrefix`
- Type: `string | false`
- Default: `/__sitemap__/`
The path prefix for the sitemaps when using multiple sitemaps.
## `runtimeCacheStorage`
- Type: `boolean | (Record<string, any> & { driver: string })`
- Default: `true`
The storage engine to use for the cache. See [Performance](/docs/sitemap/advanced/performance) for details.
## `xslColumns`
- Type: ``({ label: string; width: `${string}%`; select?: string })[]``
- Default:
```json
[
{ "label": "URL", "width": "50%" },
{ "label": "Images", "width": "25%", "select": "count(image:image)" },
{ "label": "Last Updated", "width": "25%", "select": "concat(substring(sitemap:lastmod,0,11),concat(' ', substring(sitemap:lastmod,12,5)),concat(' ', substring(sitemap:lastmod,20,6)))" }
]
```
The columns to display in the XSL stylesheet.
## `xslTips`
- Type: `boolean`
- Default: `true`
Whether to include tips on how to use the sitemap in the XSL stylesheet.
## `experimentalWarmUp`
- Type: `boolean`
- Default: `false`
Should the sitemaps be warmed up when Nitro starts. This can be useful for large sitemaps.
## `experimentalCompression`
- Type: `boolean`
- Default: `false`
Should the sitemaps be compressed and streamed when the request accepts it.
## `credits`
- Type: `boolean`
- Default: `true`
Whether to include a comment on the sitemaps on how it was generated.
## `minify`
- Type: `boolean`{lang="ts"}
- Default: `false`{lang="ts"}
Whether to minify the sitemap.xml.
## `debug`
- Type: `boolean`
- Default: `false`
Enable to see debug logs and API endpoint.
The route at `/__sitemap__/debug.json` will be available in non-production environments.
See the [Troubleshooting](/docs/sitemap/getting-started/troubleshooting) guide for details.
## `zeroRuntime`
- Type: `boolean`
- Default: `false`
When enabled, sitemap generation only runs during prerendering. The sitemap building code is tree-shaken from the runtime bundle, reducing server bundle size by ~50KB.
Requires sitemaps to be prerendered. When enabled, `/sitemap.xml` is automatically added to `nitro.prerender.routes`.
See the [Zero Runtime](/docs/sitemap/guides/zero-runtime) guide for details.
================================================
FILE: docs/content/4.api/1.nuxt-hooks.md
================================================
---
title: Nuxt Hooks
description: Build-time Nuxt hooks provided by @nuxtjs/sitemap.
---
## `'sitemap:prerender:done'`{lang="ts"}
**Type:** `(ctx: { options: ModuleRuntimeConfig, sitemaps: { name: string, readonly content: string }[] }) => void | Promise<void>`{lang="ts"}
Called after sitemap prerendering completes. Useful for modules that need to emit extra files based on the generated sitemaps.
**Context:**
- `options` - The resolved module runtime configuration
- `sitemaps` - Array of rendered sitemaps with their route name and XML content (content is lazily loaded from disk)
```ts [nuxt.config.ts]
export default defineNuxtConfig({
hooks: {
'sitemap:prerender:done': async ({ sitemaps }) => {
// Log sitemap info
for (const sitemap of sitemaps) {
console.log(`Sitemap ${sitemap.name}: ${sitemap.content.length} bytes`)
}
}
}
})
```
::note
This hook only runs at build time during `nuxt generate` or `nuxt build` with prerendering enabled.
::
================================================
FILE: docs/content/5.nitro-api/nitro-hooks.md
================================================
---
title: Nitro Hooks
description: Learn how to use Nitro Hooks to customize your sitemap entries.
relatedPages:
- path: /docs/sitemap/getting-started/data-sources
title: Data Sources
- path: /docs/sitemap/guides/dynamic-urls
title: Dynamic URL Endpoints
- path: /docs/sitemap/guides/multi-sitemaps
title: Multi Sitemaps
---
Nitro hooks can be added to modify the output of your sitemaps at runtime.
## `'sitemap:input'`{lang="ts"}
**Type:** `async (ctx: { event: H3Event; urls: SitemapUrlInput[]; sitemapName: string }) => void | Promise<void>`{lang="ts"}
Triggers once the raw list of URLs is collected from sources.
This hook is best used for inserting new URLs into the sitemap.
```ts [server/plugins/sitemap.ts]
import { defineNitroPlugin } from 'nitropack/runtime'
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('sitemap:input', async (ctx) => {
// SitemapUrlInput is either a string
ctx.urls.push('/foo')
// or an object with loc, changefreq, and priority
ctx.urls.push({
loc: '/bar',
changefreq: 'daily',
priority: 0.8,
})
})
})
```
## `'sitemap:resolved'`{lang="ts"}
**Type:** `async (ctx: { event: H3Event; urls: ResolvedSitemapUrl[]; sitemapName: string }) => void | Promise<void>`{lang="ts"}
Triggered once the final structure of the XML is generated, provides the URLs as objects.
For new URLs it's recommended to use `sitemap:input` instead. Use this hook for modifying entries or removing them.
```ts [server/plugins/sitemap.ts]
import { defineNitroPlugin } from 'nitropack/runtime'
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('sitemap:resolved', async (ctx) => {
// single sitemap example - just add the url directly
ctx.urls.push({
loc: '/my-secret-url',
changefreq: 'daily',
priority: 0.8,
})
// multi sitemap example - filter for a sitemap name
if (ctx.sitemapName === 'posts') {
ctx.urls.push({
loc: '/posts/my-post',
changefreq: 'daily',
priority: 0.8,
})
}
})
})
```
## `'sitemap:index-resolved'`{lang="ts"}
**Type:** `async (ctx: { event: H3Event; sitemaps: { sitemap: string, lastmod?: string }[] }) => void | Promise<void>`{lang="ts"}
Triggered once the final structure of the sitemap index is generated, provides the sitemaps as objects.
```ts [server/plugins/sitemap.ts]
import { defineNitroPlugin } from 'nitropack/runtime'
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('sitemap:index-resolved', async (ctx) => {
// add a new sitemap to the index
ctx.sitemaps.push({
sitemap: 'https://mysite.com/my-sitemap.xml',
lastmod: new Date().toISOString(),
})
})
})
```
## `'sitemap:output'`{lang="ts"}
**Type:** `async (ctx: { event: H3Event; sitemap: string; sitemapName: string }) => void | Promise<void>`{lang="ts"}
Triggered before the sitemap is sent to the client.
It provides the sitemap as a XML string.
```ts [server/plugins/sitemap.ts]
import { defineNitroPlugin } from 'nitropack/runtime'
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('sitemap:output', async (ctx) => {
// append a comment credit to the footer of the xml
ctx.sitemap = `${ctx.sitemap}\n<!-- Sitemap output test-->`
})
})
```
## `'sitemap:sources'`{lang="ts"}
**Type:** `async (ctx: { event: H3Event; sitemapName: string; sources: SitemapSourceInput[] }) => void | Promise<void>`{lang="ts"}
Triggered before resolving sitemap sources. This hook allows you to:
- Add new sources dynamically
- Remove sources
- Modify source configurations including fetch options and headers
This hook runs before sources are resolved, providing full control over the source list.
```ts [server/plugins/sitemap.ts]
import { defineNitroPlugin } from 'nitropack/runtime'
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('sitemap:sources', async (ctx) => {
// Add a source that will be fetched
ctx.sources.push('/api/dynamic-urls')
// Add a source with fetch options
ctx.sources.push(['/api/authenticated-urls', { headers: { 'X-Api-Key': 'secret' } }])
// Add a resolved source with URLs directly (no fetch needed)
ctx.sources.push({
context: { name: 'my-custom-source' },
urls: ['/page-1', '/page-2', { loc: '/page-3', priority: 0.8 }],
})
// Modify existing sources to add headers
ctx.sources = ctx.sources.map((source) => {
if (typeof source === 'object' && 'fetch' in source && source.fetch) {
const [url, options = {}] = Array.isArray(source.fetch) ? source.fetch : [source.fetch, {}]
// Add headers from original request
const authHeader = ctx.event.node.req.headers.authorization
if (authHeader) {
options.headers = options.headers || {}
options.headers.Authorization = authHeader
}
source.fetch = [url, options]
}
return source
})
// Filter out sources
ctx.sources = ctx.sources.filter((source) => {
if (typeof source === 'string')
return !source.includes('skip-this')
return true
})
})
})
```
## Recipes
### Modify Sitemap `xmlns` attribute
For some search engines, you may need to add a custom `xmlns` attribute to the sitemap. You can do this with a simple
search and replace in the `sitemap:output` hook.
```ts [server/plugins/sitemap.ts]
import { defineNitroPlugin } from 'nitropack/runtime'
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('sitemap:output', async (ctx) => {
ctx.sitemap = ctx.sitemap.replace('<urlset ', '<urlset xmlns:mobile="http://www.baidu.com/schemas/sitemap-mobile/1/" ')
})
})
```
### Modify Video Entries For Host
Sometimes you'll want to include the videos from your markup automatically but exclude some of them based on the host.
```ts
import { defineNitroPlugin } from 'nitropack/runtime'
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('sitemap:resolved', (ctx) => {
ctx.urls.map((url) => {
if (url.videos?.length) {
url.videos = url.videos.filter((video) => {
if (video.content_loc) {
const url = new URL(video.content_loc)
return url.host.startsWith('www.youtube.com')
}
return false
})
}
return url
})
})
})
```
================================================
FILE: docs/content/5.releases/3.v8.md
================================================
---
navigation:
title: v8.0.0
title: Nuxt Sitemap v8.0.0
description: Release notes for v8.0.0 of Nuxt Sitemap.
---
## Introduction
The v8 release focuses on a fully rewritten devtools experience and several quality of life improvements for Nuxt Content v3 and i18n users.
## ⚠️ Breaking Changes
### Site Config v4
Nuxt Site Config is a module used internally by Nuxt Sitemap.
The major update to v4.0.0 shouldn't have any direct effect on your site, however, you may want to double-check
the [breaking changes](https://github.com/harlan-zw/nuxt-site-config/releases/tag/v4.0.0).
### `asSitemapCollection()` Deprecated
The `asSitemapCollection()` composable has been replaced by `defineSitemapSchema()`. The old API still works but will log a deprecation warning.
```diff
import { defineCollection, defineContentConfig } from '@nuxt/content'
import { z } from 'zod'
- import { asSitemapCollection } from '@nuxtjs/sitemap/content'
+ import { defineSitemapSchema } from '@nuxtjs/sitemap/content'
export const collections = {
- content: defineCollection(asSitemapCollection({
- type: 'page',
- source: '**/*.md',
- schema: z.object({ title: z.string() })
- }))
+ content: defineCollection({
+ type: 'page',
+ source: '**/*.md',
+ schema: z.object({
+ title: z.string(),
+ sitemap: defineSitemapSchema()
+ })
+ })
}
```
## 🚀 New Features
### `defineSitemapSchema()` Composable
A new composable for Nuxt Content v3 that provides a cleaner API for integrating sitemap configuration into your content collections. Supports `filter`, `onUrl`, and `name` options.
```ts
import { defineCollection, defineContentConfig } from '@nuxt/content'
import { z } from 'zod'
import { defineSitemapSchema } from '@nuxtjs/sitemap/content'
export const collections = {
content: defineCollection({
type: 'page',
source: '**/*.md',
schema: z.object({
title: z.string(),
sitemap: defineSitemapSchema({
filter: entry => !entry.path?.startsWith('/draft'),
onUrl: (url) => {
// customize URL entries
return url
}
})
})
})
}
```
### `definePageMeta` Sitemap Configuration
You can now configure sitemap options directly in your page components using `definePageMeta`.
```vue
<script setup>
definePageMeta({
sitemap: {
changefreq: 'daily',
priority: 0.8
}
})
</script>
```
### i18n Multi-Sitemap with Custom Sitemaps
Custom sitemaps with `includeAppSources: true` are now automatically expanded per locale, generating `{locale}-{name}` formatted sitemaps.
### Debug Production Endpoint
A new `/__sitemap__/debug-production.json` endpoint is available in development mode, allowing you to inspect what the production sitemap output will look like during development.
## 🐛 Bug Fixes
- **Content v3**: Filter `.navigation` paths from sitemap URL generation
- **Content v3**: Guard `afterParse` hook to prevent silent HMR failures
- **i18n**: Include base URL in multi-sitemap redirect
- **i18n**: Fix exclude filters when base URL and i18n prefixes are present
- **i18n**: Respect `autoI18n: false` to generate single sitemap instead of per-locale sitemaps
- **Types**: Use `robots` instead of `index` in route rules type definition
- **Chunked sitemaps**: Fix path resolution with `sitemapsPathPrefix: '/'`
## ⚡ Performance
- Replaced `chalk` with `consola/utils` for a smaller bundle
- Use `URL.canParse()` instead of try/catch `new URL()` for URL validation
- Use `addPrerenderRoutes()` API instead of manual route pushing
================================================
FILE: docs/content/5.releases/4.v7.md
================================================
---
navigation:
title: v7.0.0
title: Nuxt Sitemap v7.0.0
description: Release notes for v7.0.0 of Nuxt Sitemap.
---
## Introduction
The v7 major of Nuxt Sitemap is a simple release to remove deprecations and add support for the [Nuxt SEO v2 stable](/announcement).
## :icon{name="i-noto-warning"} Breaking Features
### Site Config v3
Nuxt Site Config is a module used internally by Nuxt Sitemap.
The major update to v3.0.0 shouldn't have any direct effect on your site, however, you may want to double-check
the [breaking changes](https://github.com/harlan-zw/nuxt-site-config/releases/tag/v3.0.0).
### Removed `inferStaticPagesAsRoutes` config
If you set this value to `false` previously, you will need to change it to the below:
```diff
export default defineNuxtConfig({
sitemap: {
- inferStaticPagesAsRoutes: false,
+ excludeAppSources: ['nuxt:pages', 'nuxt:route-rules', 'nuxt:prerender']
}
})
```
### Removed `dynamicUrlsApiEndpoint` config
The `sources` config supports multiple API endpoints and allows you to provide custom fetch options, use this instead.
```diff
export default defineNuxtConfig({
sitemap: {
- dynamicUrlsApiEndpoint: '/__sitemap/urls',
+ sources: ['/__sitemap/urls']
}
})
```
### Removed `cacheTtl` config
Please use the `cacheMaxAgeSeconds` as its a clearer config.
```diff
export default defineNuxtConfig({
sitemap: {
- cacheTtl: 10000,
+ cacheMaxAgeSeconds: 10000
}
})
```
### Removed `index` route rule / Nuxt Content support
If you were using the `index: false` in either route rules or your Nuxt Content markdown files, you will need to update this to use the `robots` key.
```diff
export default defineNuxtConfig({
routeRules: {
// use the `index` shortcut for simple rules
- '/secret/**': { index: false },
+ '/secret/**': { robots: false },
}
})
```
================================================
FILE: docs/content/5.releases/5.v6.md
================================================
---
navigation:
title: v6.0.0
title: Nuxt Sitemap v6.0.0
description: Release notes for v6.0.0 of Nuxt Sitemap.
---
## Introduction
The v6 represents hopefully the last major that the module will undergo. It brings many underlying
logic improvements which aim to solve stability and performance issues and set up the module to support
chunked multi-sitemaps in the future.
## 🚨 Breaking Change
### Google Search Console
If you're using multi-sitemaps it's important to check Google Search Console after the update and verify you haven't submitted the old multi-sitemap paths. If so, you should update them
### Sitemap Output
Please verify your sitemap output after the update. Many changes have been made to the underlying logic and it's important to verify that your sitemap is still being generated correctly.
## Changelog
### 🚨 Breaking Changes
- Rewrite i18n resolving and url normalizing - by @harlan-zw in https://github.com/nuxt-modules/sitemap/issues/319 [<samp>(fab7e)</samp>](https://github.com/nuxt-modules/sitemap/commit/fab7e9e)
- New multi sitemaps paths - by @harlan-zw in https://github.com/nuxt-modules/sitemap/issues/320 [<samp>(bb7d9)</samp>](https://github.com/nuxt-modules/sitemap/commit/bb7d9c7)
### 🚀 Features
- `sitemapsPathPrefix` config - by @harlan-zw in https://github.com/nuxt-modules/sitemap/issues/325 [<samp>(4b94c)</samp>](https://github.com/nuxt-modules/sitemap/commit/4b94c3d)
- Add minify xml option - by @Henvy-Mango in https://github.com/nuxt-modules/sitemap/issues/336 [<samp>(f9197)</samp>](https://github.com/nuxt-modules/sitemap/commit/f919726)
- **i18n**: Support Nuxt I18n v9 - by @harlan-zw in https://github.com/nuxt-modules/sitemap/issues/351 [<samp>(92d96)</samp>](https://github.com/nuxt-modules/sitemap/commit/92d9610)
### 🐞 Bug Fixes
- Better filtering of file URLs - by @harlan-zw [<samp>(27a95)</samp>](https://github.com/nuxt-modules/sitemap/commit/27a95be)
- Check for `robots` route rules - by @harlan-zw in https://github.com/nuxt-modules/sitemap/issues/321 [<samp>(ae455)</samp>](https://github.com/nuxt-modules/sitemap/commit/ae455da)
- Map `include`, `exclude` to i18n pages - by @harlan-zw in https://github.com/nuxt-modules/sitemap/issues/322 [<samp>(a7c04)</samp>](https://github.com/nuxt-modules/sitemap/commit/a7c04bc)
- Fallback to prerender sitemap on vercel edge - by @harlan-zw [<samp>(33598)</samp>](https://github.com/nuxt-modules/sitemap/commit/33598c8)
- Support `SERVER_PRESET` to detect env - by @harlan-zw [<samp>(295c9)</samp>](https://github.com/nuxt-modules/sitemap/commit/295c98f)
- Handle null `loc`'s - by @harlan-zw [<samp>(c0666)</samp>](https://github.com/nuxt-modules/sitemap/commit/c066610)
- `useNitroApp` import warning - by @harlan-zw [<samp>(f5ab8)</samp>](https://github.com/nuxt-modules/sitemap/commit/f5ab878)
- Preset not being resolved when using `--target` - by @harlan-zw [<samp>(2f6bc)</samp>](https://github.com/nuxt-modules/sitemap/commit/2f6bca8)
- Broken regex for `<NuxtImage>` components - by @harlan-zw [<samp>(469e7)</samp>](https://github.com/nuxt-modules/sitemap/commit/469e7bd)
- Ensure `loc` is always a string - by @harlan-zw [<samp>(de9ec)</samp>](https://github.com/nuxt-modules/sitemap/commit/de9ecc2)
- Improve entry `loc` normalizing - by @harlan-zw in https://github.com/nuxt-modules/sitemap/issues/354 [<samp>(6ef8d)</samp>](https://github.com/nuxt-modules/sitemap/commit/6ef8dcd)
- **i18n**:
- Support excluded locales - by @Xenossolitarius and **ipesic** in https://github.com/nuxt-modules/sitemap/issues/331 [<samp>(f9ba0)</samp>](https://github.com/nuxt-modules/sitemap/commit/f9ba056)
- Reverse only locales logic - by @Xenossolitarius and **ipesic** in https://github.com/nuxt-modules/sitemap/issues/346 [<samp>(cc86a)</samp>](https://github.com/nuxt-modules/sitemap/commit/cc86a0c)
- Broken trailing slashes config when using `differentDomains` - by @harlan-zw [<samp>(e8799)</samp>](https://github.com/nuxt-modules/sitemap/commit/e879913)
- Broken dedupe of loc and alternatives - by @harlan-zw in https://github.com/nuxt-modules/sitemap/issues/352 [<samp>(2b164)</samp>](https://github.com/nuxt-modules/sitemap/commit/2b16423)
- **module**:
- Prevent false positive warning about ignored root keys - by @madebyfabian in https://github.com/nuxt-modules/sitemap/issues/338 [<samp>(e4543)</samp>](https://github.com/nuxt-modules/sitemap/commit/e45432b)
- **prerendering**:
- Prefer runtime site url validation - by @harlan-zw [<samp>(779d1)</samp>](https://github.com/nuxt-modules/sitemap/commit/779d100)
##### [View changes on GitHub](https://github.com/nuxt-modules/sitemap/compare/v5.3.5...v6.0.0)
================================================
FILE: docs/content/5.releases/6.v5.md
================================================
---
navigation:
title: v5.0.0
title: Nuxt Sitemap v5.0.0
description: Release notes for v5.0.0 of Nuxt Sitemap.
---
## 🚨 Breaking Changes
### Package Renamed to `@nuxtjs/sitemap`
This module is now the official Sitemap module for Nuxt. To properly
reflect this, the package has been renamed to `@nuxtjs/sitemap` from `nuxt-simple-sitemap` and
the GitHub repository has been moved to [nuxt-modules/sitemap](https://github.com/nuxt-modules/sitemap).
1. Update the dependency
```diff
{
"dependencies": {
- "nuxt-simple-sitemap": "*"
+ "@nuxtjs/sitemap": "^5.0.0"
}
}
```
2. Update your `nuxt.config`.
```diff
export default defineNuxtConfig({
modules: [
- 'nuxt-simple-sitemap'
+ '@nuxtjs/sitemap'
]
})
```
## Features :rocket:
## 🐞 Bug Fixes
### Improved Cache Debugging
- Set browser cache time to match `cacheMaxAgeSeconds` - by @harlan-zw [<samp>(00d17)</samp>](https://github.com/nuxt-modules/sitemap/commit/00d176e)
- Iso timestamp for debugging cache - by @harlan-zw [<samp>(db3f3)</samp>](https://github.com/nuxt-modules/sitemap/commit/db3f337)
- Cache headers for prerendered sitemap - by @harlan-zw [<samp>(57bef)</samp>](https://github.com/nuxt-modules/sitemap/commit/57bef21)
- More explicit caching - by @harlan-zw [<samp>(328b7)</samp>](https://github.com/nuxt-modules/sitemap/commit/328b737)
### More Consistent DevTools UI
The DevTools has been updated to match the branding of the other Nuxt SEO module DevTools. [<samp>(bc4ae)</samp>](https://github.com/nuxt-modules/sitemap/commit/bc4aebc)
### Others
- Redirect multi sitemap `sitemap.xml` using route rules - by @harlan-zw [<samp>(e1bee)</samp>](https://github.com/nuxt-modules/sitemap/commit/e1bee81)
##### [View changes on GitHub](https://github.com/nuxt-modules/sitemap/compare/v4.4.1...v5.0.0)
================================================
FILE: docs/content/5.releases/7.v4.md
================================================
---
navigation:
title: v4.0.0
title: Nuxt Sitemap v4.0.0
description: Release notes for v4.0.0 of Nuxt Sitemap.
---
## Background
Over the last couple of months I've had many issues reported with similar themes:
- Dynamic URLs are hard to work with
- It's difficult to get multiple sitemaps to show the correct URLs
- I18n has many small issues
I hope this release can resolve these. It has required replacing much of the underlying logic, please test your sitemaps after upgrading.
## Features :rocket:
### 🥫 Sitemap Sources
The v4 introduces the official concept of 'sources' to your sitemaps.
Every URL within your sitemap will belong to a source. A source will either be a User source or a Application source.
This concept existed before v4 in different forms, v4 aims to clean them up and make working with them much easier.
For full documentation see [Sitemap Sources](/docs/sitemap/getting-started/data-sources).
### 🤝 Nuxt Dev Tools Integration
Nuxt Sitemap now has a dedicated tab in Nuxt Dev Tools to help you debug.
<details open="" class="details-reset border rounded-2">
<summary class="px-3 py-2">
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-device-camera-video">
<path d="M16 3.75v8.5a.75.75 0 0 1-1.136.643L11 10.575v.675A1.75 1.75 0 0 1 9.25 13h-7.5A1.75 1.75 0 0 1 0 11.25v-6.5C0 3.784.784 3 1.75 3h7.5c.966 0 1.75.784 1.75 1.75v.675l3.864-2.318A.75.75 0 0 1 16 3.75Zm-6.5 1a.25.25 0 0 0-.25-.25h-7.5a.25.25 0 0 0-.25.25v6.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-6.5ZM11 8.825l3.5 2.1v-5.85l-3.5 2.1Z"></path>
</svg>
<span aria-label="Video description nuxt-simple-sitemap-devtools.webm" class="m-1">nuxt-simple-sitemap-devtools.webm</span>
<span class="dropdown-caret"></span>
</summary>
<video src="https://user-images.githubusercontent.com/5326365/282252319-269d8421-0704-4336-81a6-dd597fe80d38.webm" data-canonical-src="https://user-images.githubusercontent.com/5326365/282252319-269d8421-0704-4336-81a6-dd597fe80d38.webm" controls="controls" muted="muted" class="d-block rounded-bottom-2 border-top width-fit" style="max-height:640px; min-height: 200px">
</video>
</details>
### 💬 More i18n Improvements
- Locale domain support ([#155](https://github.com/nuxt-modules/sitemap/issues/155))
- Support pages opt-outed using `defineI18nRoute(false)` ([#126](https://github.com/nuxt-modules/sitemap/issues/126))
- Only add trusted i18n routes, will use meta tags when prerendering
- Less aggressive filtering
- Opt-in to transform dynamic URLs `__i18nTransform: true`
See the updated [i18n documentation](/docs/sitemap/guides/i18n)
### 🚀 Caching Improvements
Now utilises native route rules. By default will set up SWR rules for 10 minutes.
Learn more on the [Sitemap Caching](/docs/sitemap/advanced/performance) guide.
## Other Improvements
### Nitro Composables for better types
When creating an API endpoint that returns URLs you should use the new [`defineSitemapEventHandler()`{lang="ts"}](/docs/sitemap/nitro-api/nitro-hooks) function for full TypeScript support.
```ts
// api/sitemap.ts
export default defineSitemapEventHandler(() => {
return ['/foo']
})
```
### Prerendering Improvements
Previously prerendering was done in a Node context, this will now run in a Nitro context which will provide better consistency between prerender and runtime environments.
### Video Support
Video entries are now supported properly. ([#159](https://github.com/nuxt-modules/sitemap/issues/159))
## ⚠️ Deprecations
- `cacheTtl` is deprecated, you should use `cacheMaxAgeSeconds` which is more explicit.
- `inferStaticPagesAsRoutes` is deprecated, if you were using this to opt-out of pages, you should use `excludeAppSources: true`
## ☠️ Breaking Changes
### Nuxt Hooks no longer supported
If you were using Nuxt hooks to modify the prerendered sitemap, you will need to migrate these to Nitro hooks.
```ts [nuxt.config.ts]
export default defineNuxtConfig({
hooks: {
// old - no longer supported
'sitemap:resolved': function (ctx) {},
'sitemap:output': function (ctx) {}
},
})
```
```ts [server/plugins/sitemap]
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('sitemap:output', async (ctx) => {
// supported!
})
})
```
### Multi Sitemap App Sources
By default, app sources will no longer be included in multi sitemap implementations. You will need to use `includeAppSources: true` to re-enable it. See [Extending App Sources](/docs/sitemap/guides/multi-sitemaps#extending-app-sources) for more information.
### Removed deprecations
- The hook `sitemap:prerender` has been removed. You should use `sitemap:resolved` instead.
- The config `trailingSlash` and `siteUrl` has been removed. You should use site config, see [Setting Site Config](/docs/site-config/guides/how-it-works).
- The config `autoAlternativeLangPrefixes` has been removed. If you'd like to set up automatic alternative language prefixes use `__i18nTransform`.
## Support my work
This release took over 40 hours.
If technical SEO developer experience in Nuxt is important to you, consider [supporting my work](https://github.com/sponsors/harlan-zw) on Nuxt SEO.
================================================
FILE: docs/content/5.releases/8.v3.md
================================================
---
title: v3.0.0
description: Release notes for v3.0.0.
---
## Features :rocket:
### 🤝 Stable I18n Integration
Fully supporting i18n sites through the `sitemap` module has been a long requested feature.
V2 Partially supported it, but in v3 support is fully integrated with build-time macros support and more.
### 🚀 Default Caching
Caching is now enabled by default for production sitemaps. Sitemaps will be cached for 1 hour.
You can change the cache time or disable caching by setting the `cacheTtl` config.
```ts
export default defineNuxtConfig({
sitemap: {
cacheTtl: 5 * 60 * 60 * 1000 // 5 hours
}
})
```
You can also provide your own cache instance by setting the `runtimeCacheStorage` config.
```ts
export default defineNuxtConfig({
sitemap: {
runtimeCacheStorage: {
driver: 'redis',
host: 'localhost',
port: 6379,
db: 0,
}
}
})
```
Learn more on the [Performance](/docs/sitemap/advanced/performance) guide.
### Improved XML Stylesheet
The UI of the sitemap.xml page has been improved slightly. It now features more useful tips and links.
You can also customise the stylesheet with the new following config:
- `xslTips` - Toggle the tips displayed on the sitemap.xml pages.
- `xslColumns` - Customise the columns displayed on the sitemap.xml pages.
For example, you can change the columns to only show the `loc` and `lastmod` columns.
```ts
export default defineNuxtConfig({
sitemap: {
xslColumns: [
// URL column must always be set, no value needed
{ label: 'URL', width: '75%' },
{ label: 'Last Modified', value: 'sitemap:lastmod', width: '25%' },
],
},
})
```
Learn more on the [Customising the UI](/docs/sitemap/advanced/customising-ui) guide.
### Debug Mode
A `debug` config has been added
which will give access to a custom endpoint at `/api/__sitemap__/debug` which will show you
how your sitemap is being generated.
When you build your site with debug on, a `/__sitemap__/debug.json` page will be generated.
This is disabled by default and should only be enabled for debugging purposes.
## Other Improvements
## New Hook: `sitemap:output`
**Type:** `async (ctx: { sitemap: string; sitemapName: string }) => void | Promise<void>`
This will let you modify the string content of the final sitemap before it is returned from the server.
Can be ran in both Nitro (runtime) and Nuxt (prerendering).
## New Hook: `sitemap:resolved`
**Type:** `async (ctx: { sitemap: FullSitemapEntry[]; sitemapName: string }) => void | Promise<void>`
This will let you modify the final sitemap before it is turned into a string.
Can be ran in both Nitro (runtime) and Nuxt (prerendering).
### Individual multi-sitemap API endpoints `dynamicUrlsApiEndpoint`
- Type: `boolean | string`{lang="ts"}
- Default: `false`{lang="ts"}
You can now give each sitemap a unique API endpoint to fetch URLs from.
```ts
export default defineNuxtConfig({
sitemap: {
sitemaps: {
foo: {
dynamicUrlsApiEndpoint: '/api/foo-sitemap'
},
bar: {
dynamicUrlsApiEndpoint: '/api/bar-sitemap'
},
},
}
})
```
### New Config: `strictNuxtContentPaths`
- Type: `boolean`{lang="ts"}
- Default: `false`{lang="ts"}
Enable when the paths of your nuxt/content md files match the routing.
This will automatically add sitemap content to the sitemap.
This is similar behaviour to using `nuxt/content` with `documentDriven: true`.
### New Config: `credits`
- Type: `boolean`{lang="ts"}
- Default: `true`{lang="ts"}
Allows you to remove the "Generate by Nuxt Sitemap" comment from the generated sitemap.
### New Config: `xslTips`
- Type: `boolean`{lang="ts"}
- Default: `true`{lang="ts"}
Toggle the tips displayed on the sitemap.xml pages.
## Deprecation
- `trailingSlash` has been deprecated
- `siteUrl` has been deprecated
- `autoAlternativeLangPrefixes` is now disabled by default. If you want to enable it, you need to set it to `true` explicitly.
================================================
FILE: eslint.config.mjs
================================================
import antfu from '@antfu/eslint-config'
import harlanzw from 'eslint-plugin-harlanzw'
export default antfu(
{
type: 'lib',
ignores: [
'CLAUDE.md',
'test/fixtures/**',
'playground/**',
'docs/**',
'benchmark/**',
],
rules: {
'no-use-before-define': 'off',
'node/prefer-global/process': 'off',
'node/prefer-global/buffer': 'off',
'ts/explicit-function-return-type': 'off',
'e18e/prefer-static-regex': 'off',
},
},
{
files: ['**/test/**/*.ts', '**/test/**/*.js'],
rules: {
'ts/no-unsafe-function-type': 'off',
'no-console': 'off',
'antfu/no-top-level-await': 'off',
},
},
...harlanzw({ link: true, nuxt: true, vue: true }),
{
files: ['**/server/**/*.ts', '**/src/**/*.ts'],
rules: {
'harlanzw/vue-no-faux-composables': 'off',
},
},
{
files: ['examples/**/package.json'],
rules: {
'pnpm/json-enforce-catalog': 'off',
'pnpm/json-valid-catalog': 'off',
'pnpm/json-prefer-workspace-settings': 'off',
},
},
)
================================================
FILE: examples/basic/app/app.vue
================================================
<template>
<div>
<h1>Sitemap Basic Example</h1>
<nav>
<NuxtLink to="/">
Home
</NuxtLink>
<NuxtLink to="/about">
About
</NuxtLink>
<NuxtLink to="/contact">
Contact
</NuxtLink>
</nav>
<p>
<a href="/sitemap.xml">View Sitemap</a>
</p>
<NuxtPage />
</div>
</template>
================================================
FILE: examples/basic/app/pages/about.vue
================================================
<template>
<div>
<h2>About</h2>
<p>This is the about page.</p>
</div>
</template>
================================================
FILE: examples/basic/app/pages/contact.vue
================================================
<template>
<div>
<h2>Contact</h2>
<p>This is the contact page.</p>
</div>
</template>
================================================
FILE: examples/basic/app/pages/index.vue
================================================
<template>
<div>
<h2>Home</h2>
<p>Welcome to the basic sitemap example.</p>
</div>
</template>
================================================
FILE: examples/basic/nuxt.config.ts
================================================
export default defineNuxtConfig({
modules: ['@nuxtjs/sitemap'],
site: {
url: 'https://example.com',
},
compatibilityDate: '2025-01-01',
})
================================================
FILE: examples/basic/package.json
================================================
{
"name": "nuxtjs-sitemap-basic-example",
"type": "module",
"private": true,
"dependencies": {
"@nuxtjs/sitemap": "latest",
"nuxt": "^4.4.4",
"vue": "^3.5.33"
}
}
================================================
FILE: examples/basic/tsconfig.json
================================================
{
"extends": "./.nuxt/tsconfig.json"
}
================================================
FILE: examples/dynamic-urls/app/app.vue
================================================
<template>
<div>
<h1>Sitemap Dynamic URLs
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
SYMBOL INDEX (172 symbols across 38 files)
FILE: benchmark/bench.mjs
constant TARGET (line 20) | const TARGET = process.env.BENCH_TARGET || '/api/ping'
constant PORT (line 21) | const PORT = Number(process.env.BENCH_PORT || 3777)
constant DURATION (line 22) | const DURATION = Number(process.env.BENCH_DURATION || 10)
constant CONNECTIONS (line 23) | const CONNECTIONS = Number(process.env.BENCH_CONNECTIONS || 100)
constant SITEMAP_ARTEFACTS (line 25) | const SITEMAP_ARTEFACTS = [
constant SITEMAP_MARKERS (line 31) | const SITEMAP_MARKERS = ['@nuxtjs/sitemap', 'useSitemapRuntimeConfig', '...
function isolate (line 33) | function isolate(label) {
function assertSitemapPresence (line 41) | function assertSitemapPresence({ outDir, expectSitemap, label }) {
function run (line 83) | async function run(label, env, expectSitemap) {
FILE: devtools/composables/state.ts
function refreshSources (line 21) | async function refreshSources() {
function refreshProductionData (line 26) | async function refreshProductionData() {
FILE: src/content.ts
type DefineSitemapSchemaOptions (line 21) | interface DefineSitemapSchemaOptions<TEntry = Record<string, unknown>> {
type SitemapSchema (line 54) | type SitemapSchema = TypeOf<typeof schema>
function asSitemapCollection (line 57) | function asSitemapCollection<T>(collection: Collection<T>, options?: Def...
function warnIfZodMismatch (line 80) | function warnIfZodMismatch(userZ?: typeof z) {
function buildSitemapObjectSchema (line 105) | function buildSitemapObjectSchema(_z: typeof z) {
FILE: src/devtools.ts
function setupDevToolsUI (line 7) | function setupDevToolsUI(_options: ModuleOptions, resolve: Resolver['res...
FILE: src/module.ts
type ModuleOptions (line 58) | interface ModuleOptions extends _ModuleOptions {}
type ModuleHooks (line 60) | interface ModuleHooks {
type NuxtHooks (line 71) | interface NuxtHooks extends ModuleHooks {}
method setup (line 131) | async setup(config, nuxt) {
FILE: src/prerender.ts
function formatPrerenderRoute (line 18) | function formatPrerenderRoute(route: PrerenderRoute) {
function includesSitemapRoot (line 30) | function includesSitemapRoot(sitemapName: string, routes: string[]) {
function setupPrerenderHandler (line 36) | function setupPrerenderHandler(_options: { runtimeConfig: ModuleRuntimeC...
function prerenderSitemapsFromEntry (line 143) | async function prerenderSitemapsFromEntry(nitro: Nitro, entry: string) {
function prerenderRoute (line 164) | async function prerenderRoute(nitro: Nitro, route: string) {
FILE: src/runtime/server/composables/asSitemapUrl.ts
function asSitemapUrl (line 3) | function asSitemapUrl(url: SitemapUrlInput | Record<string, any>): Sitem...
FILE: src/runtime/server/kit.ts
function withoutQuery (line 7) | function withoutQuery(path: string): string {
function createNitroRouteRuleMatcher (line 11) | function createNitroRouteRuleMatcher(): (pathOrUrl: string) => NitroRout...
FILE: src/runtime/server/plugins/compression.ts
function getPreferredEncoding (line 5) | function getPreferredEncoding(event: H3Event): 'gzip' | 'deflate' | null {
FILE: src/runtime/server/plugins/nuxt-content-v2.ts
type NuxtContentDocument (line 7) | interface NuxtContentDocument {
FILE: src/runtime/server/robots-polyfill/getPathRobotConfig.ts
function getPathRobotConfig (line 3) | function getPathRobotConfig(_e: H3Event, _options: any) {
FILE: src/runtime/server/routes/__sitemap__/debug-production.ts
type ProductionSitemapEntry (line 5) | interface ProductionSitemapEntry {
type ProductionDebugResponse (line 13) | interface ProductionDebugResponse {
function fetchXml (line 21) | async function fetchXml(url: string): Promise<string> {
FILE: src/runtime/server/routes/__sitemap__/debug.ts
function attachUrlWarnings (line 12) | function attachUrlWarnings(sources: SitemapSourceResolved[]) {
FILE: src/runtime/server/routes/__sitemap__/nuxt-content-urls-v2.ts
type ContentWithSitemap (line 5) | interface ContentWithSitemap {
FILE: src/runtime/server/routes/__sitemap__/nuxt-content-urls-v3.ts
type ContentEntry (line 7) | interface ContentEntry {
FILE: src/runtime/server/sitemap/builder/sitemap-index.ts
constant SERVER_CACHE_MAX_AGE (line 17) | const SERVER_CACHE_MAX_AGE = (staticConfig.cacheMaxAgeSeconds as number ...
function buildSitemapIndexInternal (line 39) | async function buildSitemapIndexInternal(resolvers: NitroUrlResolvers, r...
function urlsToIndexXml (line 148) | function urlsToIndexXml(sitemaps: SitemapIndexEntry[], resolvers: NitroU...
function buildSitemapIndex (line 195) | async function buildSitemapIndex(resolvers: NitroUrlResolvers, runtimeCo...
FILE: src/runtime/server/sitemap/builder/sitemap.ts
constant SERVER_CACHE_MAX_AGE (line 27) | const SERVER_CACHE_MAX_AGE = (staticConfig.cacheMaxAgeSeconds as number ...
type NormalizedI18n (line 29) | interface NormalizedI18n extends ResolvedSitemapUrl {
function resolveSitemapEntries (line 35) | function resolveSitemapEntries(sitemap: SitemapDefinition, urls: Sitemap...
type ResolvedSitemapUrlsResult (line 234) | interface ResolvedSitemapUrlsResult {
function buildResolvedSitemapUrls (line 242) | async function buildResolvedSitemapUrls(
function getResolvedSitemapUrls (line 347) | async function getResolvedSitemapUrls(
function buildSitemapUrls (line 363) | async function buildSitemapUrls(sitemap: SitemapDefinition, resolvers: N...
FILE: src/runtime/server/sitemap/builder/xml.ts
function escapeValueForXml (line 5) | function escapeValueForXml(value: boolean | string | number): string {
function yesNo (line 11) | function yesNo(v: boolean | string) {
constant URLSET_OPENING_TAG (line 15) | const URLSET_OPENING_TAG = '<urlset xmlns:xsi="http://www.w3.org/2001/XM...
function buildUrlXml (line 17) | function buildUrlXml(url: ResolvedSitemapUrl, NL: string, I1: string, I2...
function urlsToXml (line 128) | function urlsToXml(
FILE: src/runtime/server/sitemap/event-handlers.ts
function sitemapXmlEventHandler (line 10) | async function sitemapXmlEventHandler(e: H3Event) {
function sitemapIndexXmlEventHandler (line 19) | async function sitemapIndexXmlEventHandler(e: H3Event) {
function sitemapChildXmlEventHandler (line 63) | async function sitemapChildXmlEventHandler(e: H3Event) {
FILE: src/runtime/server/sitemap/nitro.ts
constant SERVER_CACHE_MAX_AGE (line 28) | const SERVER_CACHE_MAX_AGE = (staticConfig.cacheMaxAgeSeconds as number ...
type SitemapNitroApp (line 30) | interface SitemapNitroApp extends NitroApp {
function useNitroUrlResolvers (line 34) | function useNitroUrlResolvers(e: H3Event): NitroUrlResolvers {
function buildSitemapXml (line 52) | async function buildSitemapXml(event: H3Event, definition: SitemapDefini...
function createSitemap (line 191) | async function createSitemap(event: H3Event, definition: SitemapDefiniti...
FILE: src/runtime/server/sitemap/urlset/normalise.ts
constant VALID_CHANGEFREQ (line 20) | const VALID_CHANGEFREQ = ['always', 'hourly', 'daily', 'weekly', 'monthl...
function validateSitemapUrl (line 22) | function validateSitemapUrl(url: SitemapUrlInput): string[] {
function resolve (line 43) | function resolve(s: string | URL | undefined, resolvers?: NitroUrlResolv...
function removeTrailingSlash (line 57) | function removeTrailingSlash(s: string) {
function preNormalizeEntry (line 63) | function preNormalizeEntry(_e: SitemapUrl | string, resolvers?: NitroUrl...
function isEncoded (line 110) | function isEncoded(url: string) {
function normaliseEntry (line 120) | function normaliseEntry(_e: ResolvedSitemapUrl, defaults: Omit<SitemapUr...
constant IS_VALID_W3C_DATE (line 175) | const IS_VALID_W3C_DATE = [
function isValidW3CDate (line 181) | function isValidW3CDate(d: string) {
function normaliseDate (line 186) | function normaliseDate(d: Date | string) {
FILE: src/runtime/server/sitemap/urlset/sort.ts
function sortInPlace (line 6) | function sortInPlace<T extends SitemapUrlInput[] | ResolvedSitemapUrl[]>...
FILE: src/runtime/server/sitemap/urlset/sources.ts
function normalizeSourceInput (line 16) | function normalizeSourceInput(source: SitemapSourceInput): SitemapSource...
function tryFetchWithFallback (line 28) | async function tryFetchWithFallback(url: string, options: any, event?: H...
function fetchDataSource (line 59) | async function fetchDataSource(input: SitemapSourceBase | SitemapSourceR...
function globalSitemapSources (line 166) | async function globalSitemapSources() {
function childSitemapSources (line 180) | async function childSitemapSources(definition: ModuleRuntimeConfig['site...
function resolveSitemapSources (line 198) | async function resolveSitemapSources(sources: SitemapSourceInput[], even...
FILE: src/runtime/server/sitemap/utils/chunk.ts
type ChunkInfo (line 3) | interface ChunkInfo {
function parseChunkInfo (line 10) | function parseChunkInfo(
function getSitemapConfig (line 59) | function getSitemapConfig(
function sliceUrlsForChunk (line 93) | function sliceUrlsForChunk<T>(
FILE: src/runtime/server/utils.ts
function xmlEscape (line 11) | function xmlEscape(str: string | number | boolean | Date): string {
function useSitemapRuntimeConfig (line 20) | function useSitemapRuntimeConfig(e?: H3Event): ModuleRuntimeConfig {
FILE: src/runtime/types.ts
type ModuleOptions (line 9) | interface ModuleOptions extends SitemapDefinition {
type IndexSitemapRemotes (line 167) | interface IndexSitemapRemotes {
type MultiSitemapEntry (line 171) | interface MultiSitemapEntry {
type MultiSitemapsInput (line 175) | type MultiSitemapsInput = Partial<MultiSitemapEntry> & Partial<IndexSite...
type MaybeFunction (line 177) | type MaybeFunction<T> = T | (() => T)
type MaybePromise (line 178) | type MaybePromise<T> = T | Promise<T>
type SitemapUrlInput (line 180) | type SitemapUrlInput = SitemapUrl | string
type SitemapSourceBase (line 182) | interface SitemapSourceBase {
type SitemapSourceResolved (line 192) | interface SitemapSourceResolved extends Omit<SitemapSourceBase, 'urls'> {
type AppSourceContext (line 200) | type AppSourceContext = 'nuxt:pages' | 'nuxt:prerender' | 'nuxt:route-ru...
type SitemapSourceInput (line 202) | type SitemapSourceInput = string | [string, FetchOptions] | SitemapSourc...
type LocaleObject (line 205) | interface LocaleObject extends Record<string, any> {
type AutoI18nConfig (line 228) | interface AutoI18nConfig {
type ModuleRuntimeConfig (line 236) | interface ModuleRuntimeConfig extends Pick<ModuleOptions, 'sitemapsPathP...
type SitemapIndexEntry (line 246) | interface SitemapIndexEntry {
type FilterInput (line 255) | type FilterInput = (string | RegExp | {
type ResolvedSitemapUrl (line 258) | type ResolvedSitemapUrl = Omit<SitemapUrl, 'url'> & Required<Pick<Sitema...
type SitemapDefinition (line 281) | interface SitemapDefinition {
type NitroBaseHook (line 372) | interface NitroBaseHook {
type SitemapIndexRenderCtx (line 376) | interface SitemapIndexRenderCtx extends NitroBaseHook {
type SitemapRenderCtx (line 380) | interface SitemapRenderCtx extends NitroBaseHook {
type SitemapInputCtx (line 385) | interface SitemapInputCtx extends NitroBaseHook {
type SitemapOutputHookCtx (line 390) | interface SitemapOutputHookCtx extends NitroBaseHook {
type SitemapSourcesHookCtx (line 395) | interface SitemapSourcesHookCtx extends NitroBaseHook {
type Changefreq (line 400) | type Changefreq
type SitemapUrl (line 409) | interface SitemapUrl {
type SitemapItemDefaults (line 441) | type SitemapItemDefaults = Omit<SitemapUrl, 'loc'>
type SitemapStrict (line 443) | type SitemapStrict = Required<SitemapUrl>
type AlternativeEntry (line 445) | interface AlternativeEntry {
type GoogleNewsEntry (line 450) | interface GoogleNewsEntry {
type ImageEntry (line 477) | interface ImageEntry {
type VideoEntry (line 485) | interface VideoEntry {
type Restriction (line 515) | interface Restriction {
type Platform (line 519) | interface Platform {
type NitroUrlResolvers (line 524) | interface NitroUrlResolvers {
type I18nIntegrationOptions (line 531) | type I18nIntegrationOptions = NuxtI18nOptions
FILE: src/runtime/utils-pure.ts
function mergeOnKey (line 23) | function mergeOnKey<T, K extends keyof T>(arr: T[], key: K): T[] {
function splitForLocales (line 48) | function splitForLocales(path: string, locales: string[]): [string | nul...
function normalizeRuntimeFilters (line 62) | function normalizeRuntimeFilters(input?: FilterInput[]): (RegExp | strin...
function createPathFilter (line 74) | function createPathFilter(options: { include?: (FilterInput | string | R...
type PageMatch (line 96) | interface PageMatch {
function findPageMapping (line 101) | function findPageMapping(pathWithoutPrefix: string, pages: Record<string...
function applyDynamicParams (line 122) | function applyDynamicParams(customPath: string, paramSegments: string[])...
FILE: src/templates.ts
function registerTypeTemplates (line 3) | function registerTypeTemplates() {
FILE: src/utils-internal/filter.ts
function isValidFilter (line 8) | function isValidFilter(filter: FilterInput): boolean {
function normalizeFilters (line 22) | function normalizeFilters(filters: FilterInput[] | undefined) {
FILE: src/utils-internal/i18n.ts
function splitPathForI18nLocales (line 8) | function splitPathForI18nLocales(path: FilterInput, autoI18n: AutoI18nCo...
FILE: src/utils-internal/nuxtSitemap.ts
function resolveUrls (line 13) | async function resolveUrls(urls: Required<SitemapDefinition>['urls'], ct...
type NuxtPagesToSitemapEntriesOptions (line 37) | interface NuxtPagesToSitemapEntriesOptions {
type PageEntry (line 49) | interface PageEntry extends SitemapUrl {
function deepForEachPage (line 55) | function deepForEachPage(
function convertNuxtPagesToSitemapEntries (line 96) | function convertNuxtPagesToSitemapEntries(pages: NuxtPage[], config: Nux...
function generateExtraRoutesFromNuxtConfig (line 226) | function generateExtraRoutesFromNuxtConfig(nuxt: Nuxt = useNuxt()) {
FILE: src/utils/parseHtmlExtractSitemapMeta.ts
function isValidUrl (line 7) | function isValidUrl(url: string): boolean {
function isValidString (line 29) | function isValidString(value: unknown): value is string {
function sanitizeString (line 33) | function sanitizeString(value: unknown): string {
function isValidDate (line 40) | function isValidDate(dateString: string): boolean {
function parseHtmlExtractSitemapMeta (line 47) | function parseHtmlExtractSitemapMeta(html: string, options?: { images?: ...
FILE: src/utils/parseSitemapIndex.ts
type SitemapIndexEntry (line 3) | interface SitemapIndexEntry {
type SitemapIndexParseResult (line 8) | interface SitemapIndexParseResult {
type ParsedSitemap (line 13) | interface ParsedSitemap {
type ParsedSitemapIndex (line 18) | interface ParsedSitemapIndex {
type ParsedRoot (line 22) | interface ParsedRoot {
function isValidUrl (line 26) | function isValidUrl(value: string): boolean {
function parseSitemapIndex (line 30) | async function parseSitemapIndex(xml: string): Promise<SitemapIndexParse...
function isSitemapIndex (line 81) | function isSitemapIndex(xml: string): boolean {
FILE: src/utils/parseSitemapXml.ts
type ParsedUrl (line 3) | interface ParsedUrl {
type ParsedImage (line 14) | interface ParsedImage {
type ParsedVideo (line 18) | interface ParsedVideo {
type ParsedPrice (line 48) | interface ParsedPrice {
type ParsedLink (line 54) | interface ParsedLink {
type ParsedNews (line 60) | interface ParsedNews {
type ParsedUrlset (line 69) | interface ParsedUrlset {
type ParsedRoot (line 73) | interface ParsedRoot {
type SitemapWarning (line 77) | interface SitemapWarning {
type SitemapParseResult (line 87) | interface SitemapParseResult {
function isValidString (line 92) | function isValidString(value: unknown): value is string {
function parseNumber (line 96) | function parseNumber(value: unknown): number | undefined {
function parseInteger (line 106) | function parseInteger(value: unknown): number | undefined {
function extractUrlFromParsedElement (line 116) | function extractUrlFromParsedElement(
function parseSitemapXml (line 503) | async function parseSitemapXml(xml: string): Promise<SitemapParseResult> {
FILE: test/bench/sitemap.bench.ts
type NormalizedI18n (line 7) | interface NormalizedI18n extends ResolvedSitemapUrl {
function getPageKey (line 13) | function getPageKey(pathWithoutPrefix: string): string {
function resolveSitemapEntries (line 19) | function resolveSitemapEntries(sitemap: SitemapDefinition, urls: Sitemap...
FILE: test/e2e/global-setup.ts
function setup (line 7) | async function setup() {
FILE: test/e2e/single/news.test.ts
method urls (line 11) | urls() {
FILE: test/unit/parseHtmlExtractSitemapMeta.test.ts
method resolveUrl (line 331) | resolveUrl(s) {
Condensed preview — 422 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (922K chars).
[
{
"path": ".attw.json",
"chars": 82,
"preview": "{\n \"ignoreRules\": [\"cjs-resolves-to-esm\", \"false-export-default\", \"false-esm\"]\n}\n"
},
{
"path": ".claude/skills/nuxt-site-config-skilld/SKILL.md",
"chars": 1395,
"preview": "---\nname: nuxt-site-config-skilld\ndescription: \"Shared site configuration for Nuxt 3 modules. ALWAYS use when writing co"
},
{
"path": ".claude/skills/nuxt-test-utils-skilld/SKILL.md",
"chars": 8098,
"preview": "---\nname: nuxt-test-utils-skilld\ndescription: \"ALWAYS use when writing code importing \\\"@nuxt/test-utils\\\". Consult for "
},
{
"path": ".claude/skills/skilld-lock.yaml",
"chars": 798,
"preview": "skills:\n nuxt-test-utils-skilld:\n packageName: '@nuxt/test-utils'\n version: 4.0.0\n repo: nuxt/test-utils\n s"
},
{
"path": ".claude/skills/vitest-skilld/SKILL.md",
"chars": 8496,
"preview": "---\nname: vitest-skilld\ndescription: \"ALWAYS use when writing code importing \\\"vitest\\\". Consult for debugging, best pra"
},
{
"path": ".editorconfig",
"chars": 188,
"preview": "root = true\n\n[*]\nindent_size = 2\nindent_style = space\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ni"
},
{
"path": ".github/FUNDING.yml",
"chars": 20,
"preview": "github: [harlan-zw]\n"
},
{
"path": ".github/ISSUE_TEMPLATE/01-feature-suggestion.yml",
"chars": 841,
"preview": "name: 🆕 Feature suggestion\ndescription: Suggest an idea!\ntitle: 'feat: '\nlabels: [enhancement]\nbody:\n - type: textarea\n"
},
{
"path": ".github/ISSUE_TEMPLATE/02-bug-report.yml",
"chars": 1152,
"preview": "name: 🐛 Bug report\ndescription: Something's not working\ntitle: 'fix: '\nlabels: [bug]\nbody:\n - type: textarea\n valida"
},
{
"path": ".github/ISSUE_TEMPLATE/03-documentation.yml",
"chars": 698,
"preview": "name: 📚 Documentation\ndescription: How do I ... ?\ntitle: 'docs: '\nlabels: [documentation]\nbody:\n - type: textarea\n v"
},
{
"path": ".github/ISSUE_TEMPLATE/04-help-wanted.yml",
"chars": 629,
"preview": "name: 🆘 Help\ndescription: I need help with ...\ntitle: 'help: '\nlabels: [help wanted]\nbody:\n - type: textarea\n valida"
},
{
"path": ".github/ISSUE_TEMPLATE/config.yml",
"chars": 320,
"preview": "contact_links:\n - name: 📖 Documentation\n url: https://nuxtseo.com/sitemap/getting-started/installation\n about: Ch"
},
{
"path": ".github/pull_request_template.md",
"chars": 874,
"preview": "<!---\n☝️ PR title should follow conventional commits (https://conventionalcommits.org)\n-->\n\n### 🔗 Linked issue\n\n<!-- If "
},
{
"path": ".github/renovate.json5",
"chars": 170,
"preview": "{\n // https://github.com/nuxt/renovate-config-nuxt\n \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n "
},
{
"path": ".github/workflows/nightly.yml",
"chars": 214,
"preview": "name: Nightly\n\non:\n pull_request:\n push:\n branches:\n - main\n tags:\n - '!**'\n\npermissions:\n contents: "
},
{
"path": ".github/workflows/release.yml",
"chars": 202,
"preview": "name: Release\n\non:\n push:\n tags:\n - 'v*'\n\njobs:\n release:\n permissions:\n contents: write\n id-toke"
},
{
"path": ".github/workflows/test.yml",
"chars": 307,
"preview": "name: CI\n\non:\n push:\n paths-ignore:\n - '**/README.md'\n - 'docs/**'\n\nconcurrency:\n group: ${{ github.workf"
},
{
"path": ".gitignore",
"chars": 319,
"preview": "node_modules\ndist\n.output\n.nuxt\n.temp\n.tmp\n.cache\n.idea\n.vscode\n*.swp\n.DS_Store\n*.log\ncoverage\n.env\n.env.*\n!.env.example"
},
{
"path": ".npmrc",
"chars": 22,
"preview": "shamefully-hoist=true\n"
},
{
"path": ".nuxtrc",
"chars": 117,
"preview": "imports.autoImport=false\ntypescript.includeWorkspace=true\nmodules.0=\"@nuxtjs/sitemap\"\nsetups.@nuxt/test-utils=\"4.0.0\""
},
{
"path": "LICENSE.md",
"chars": 1070,
"preview": "MIT License\n\nCopyright (c) 2024 Harlan Wilton\n\nPermission is hereby granted, free of charge, to any person obtaining a c"
},
{
"path": "README.md",
"chars": 3212,
"preview": "<h1>@nuxtjs/sitemap</h1>\n\n[![npm version][npm-version-src]][npm-version-href]\n[![npm downloads][npm-downloads-src]][npm-"
},
{
"path": "SECURITY.md",
"chars": 1099,
"preview": "# Security Policy\n\n## Reporting a Vulnerability\n\nI take the security of my Nuxt modules seriously. If you believe you've"
},
{
"path": "benchmark/app/app.vue",
"chars": 42,
"preview": "<template>\n <div>bench</div>\n</template>\n"
},
{
"path": "benchmark/bench.mjs",
"chars": 6036,
"preview": "// Minimal throughput benchmark for @nuxtjs/sitemap\n// Usage:\n// node benchmark/bench.mjs # all variants\n"
},
{
"path": "benchmark/nuxt.config.ts",
"chars": 1342,
"preview": "const enableSitemap = process.env.BENCH_SITEMAP === '1'\nconst enableWarmUp = process.env.BENCH_WARMUP !== '0'\nconst enab"
},
{
"path": "benchmark/package.json",
"chars": 262,
"preview": "{\n \"name\": \"sitemap-benchmark\",\n \"type\": \"module\",\n \"private\": true,\n \"scripts\": {\n \"bench\": \"node bench.mjs\"\n }"
},
{
"path": "benchmark/server/api/ping.get.ts",
"chars": 97,
"preview": "import { defineEventHandler } from 'h3'\n\nexport default defineEventHandler(() => ({ ok: true }))\n"
},
{
"path": "build.config.ts",
"chars": 529,
"preview": "import { defineBuildConfig } from 'unbuild'\n\nexport default defineBuildConfig({\n declaration: true,\n entries: [\n { "
},
{
"path": "devtools/app.config.ts",
"chars": 439,
"preview": "export default {\n ui: {\n colors: {\n primary: 'green',\n neutral: 'neutral',\n },\n button: {\n defa"
},
{
"path": "devtools/app.vue",
"chars": 3474,
"preview": "<script setup lang=\"ts\">\nimport { loadShiki } from 'nuxtseo-layer-devtools/composables/shiki'\nimport { isProductionMode "
},
{
"path": "devtools/components/Source.vue",
"chars": 3956,
"preview": "<script setup lang=\"ts\">\nimport type { SitemapSourceResolved } from '../../src/runtime/types'\nimport { joinURL } from 'u"
},
{
"path": "devtools/composables/rpc.ts",
"chars": 187,
"preview": "import { useDevtoolsConnection } from 'nuxtseo-layer-devtools/composables/rpc'\nimport { refreshSources } from './state'\n"
},
{
"path": "devtools/composables/state.ts",
"chars": 2597,
"preview": "import type { ProductionDebugResponse } from '../../src/runtime/server/routes/__sitemap__/debug-production'\nimport type "
},
{
"path": "devtools/nuxt.config.ts",
"chars": 408,
"preview": "import { resolve } from 'pathe'\n\nexport default defineNuxtConfig({\n extends: ['nuxtseo-layer-devtools'],\n\n sitemap: fa"
},
{
"path": "devtools/package.json",
"chars": 389,
"preview": "{\n \"name\": \"@nuxtjs/sitemap-client\",\n \"private\": true,\n \"devDependencies\": {\n \"@iconify-json/carbon\": \"catalog:\",\n"
},
{
"path": "devtools/pages/app-sources.vue",
"chars": 949,
"preview": "<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport Source from '../components/Source.vue'\nimport { data } fr"
},
{
"path": "devtools/pages/debug.vue",
"chars": 526,
"preview": "<script setup lang=\"ts\">\nimport { data } from '../composables/state'\n</script>\n\n<template>\n <div class=\"space-y-5 anima"
},
{
"path": "devtools/pages/docs.vue",
"chars": 76,
"preview": "<template>\n <DevtoolsDocs url=\"https://nuxtseo.com/sitemap\" />\n</template>\n"
},
{
"path": "devtools/pages/index.vue",
"chars": 14759,
"preview": "<script setup lang=\"ts\">\nimport type { SitemapDefinition, SitemapSourceResolved } from '../../src/runtime/types'\nimport "
},
{
"path": "devtools/pages/user-sources.vue",
"chars": 933,
"preview": "<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport Source from '../components/Source.vue'\nimport { data } fr"
},
{
"path": "devtools/tsconfig.json",
"chars": 41,
"preview": "{\n \"extends\": \"./.nuxt/tsconfig.json\"\n}\n"
},
{
"path": "docs/content/0.getting-started/0.introduction.md",
"chars": 2029,
"preview": "---\ntitle: 'Nuxt Sitemap'\ndescription: 'Powerfully flexible XML Sitemaps that integrate seamlessly, for Nuxt.'\nnavigatio"
},
{
"path": "docs/content/0.getting-started/1.installation.md",
"chars": 3425,
"preview": "---\ntitle: 'Install Nuxt Sitemap'\ndescription: 'Get started with Nuxt Sitemap by installing the dependency to your proje"
},
{
"path": "docs/content/0.getting-started/2.data-sources.md",
"chars": 4580,
"preview": "---\ntitle: Data Sources\ndescription: Understand where your sitemap URLs come from.\nnavigation:\n title: 'Data Sources'\n-"
},
{
"path": "docs/content/0.getting-started/3.troubleshooting.md",
"chars": 6336,
"preview": "---\ntitle: \"Troubleshooting Nuxt Sitemap\"\ndescription: Common issues and debugging tips for Nuxt Sitemap.\nnavigation:\n "
},
{
"path": "docs/content/1.guides/0.dynamic-urls.md",
"chars": 7032,
"preview": "---\ntitle: Dynamic URL Endpoints\ndescription: Use runtime API endpoints to generate dynamic URLs for your sitemap.\nrelat"
},
{
"path": "docs/content/1.guides/1.filtering-urls.md",
"chars": 3001,
"preview": "---\ntitle: Disabling Indexing\ndescription: How to filter the URLs generated from application sources.\nrelatedPages:\n - "
},
{
"path": "docs/content/1.guides/2.multi-sitemaps.md",
"chars": 6582,
"preview": "---\ntitle: Multi Sitemaps\ndescription: Generate multiple sitemaps for different sections of your site.\nrelatedPages:\n -"
},
{
"path": "docs/content/1.guides/3.i18n.md",
"chars": 5325,
"preview": "---\ntitle: I18n\ndescription: Setting up a sitemap with Nuxt I18n and Nuxt I18n Micro.\nrelatedPages:\n - path: /docs/site"
},
{
"path": "docs/content/1.guides/4.content.md",
"chars": 8886,
"preview": "---\ntitle: Nuxt Content\ndescription: How to use the Nuxt Sitemap module with Nuxt Content.\nrelatedPages:\n - path: /docs"
},
{
"path": "docs/content/1.guides/5.prerendering.md",
"chars": 3440,
"preview": "---\ntitle: Nuxt Prerendering\ndescription: Prerender your pages and have them all automatically added to your sitemap.\nre"
},
{
"path": "docs/content/1.guides/6.best-practices.md",
"chars": 2839,
"preview": "---\ntitle: Sitemap.xml Best Practices\ndescription: The best practices for generating a sitemap.xml file.\nnavigation:\n t"
},
{
"path": "docs/content/1.guides/7.submitting-sitemap.md",
"chars": 2782,
"preview": "---\ntitle: 'Submitting Your Sitemap'\ndescription: 'How to submit your sitemap to Google Search Console to start getting "
},
{
"path": "docs/content/1.guides/8.zero-runtime.md",
"chars": 2087,
"preview": "---\ntitle: Zero Runtime\ndescription: Generate sitemaps at build time without runtime overhead.\n---\n\nIf your sitemap URLs"
},
{
"path": "docs/content/2.advanced/0.loc-data.md",
"chars": 4178,
"preview": "---\ntitle: Lastmod, Priority, and Changefreq\ndescription: Configure lastmod, priority, and changefreq values for your si"
},
{
"path": "docs/content/2.advanced/1.images-videos.md",
"chars": 7893,
"preview": "---\ntitle: Images, Videos, News\ndescription: Learn how to add images, videos and news in your sitemap.\nrelatedPages:\n -"
},
{
"path": "docs/content/2.advanced/2.performance.md",
"chars": 4522,
"preview": "---\ntitle: Sitemap Performance\ndescription: Use the default cache engine to keep your sitemaps fast.\nrelatedPages:\n - p"
},
{
"path": "docs/content/2.advanced/3.chunking-sources.md",
"chars": 4425,
"preview": "---\ntitle: Sitemap Chunking\ndescription: Split large sitemap sources into multiple files for performance and search engi"
},
{
"path": "docs/content/2.advanced/4.customising-ui.md",
"chars": 2235,
"preview": "---\ntitle: Customising the UI\ndescription: Change the look and feel of your sitemap.\nrelatedPages:\n - path: /docs/sitem"
},
{
"path": "docs/content/4.api/0.config.md",
"chars": 9259,
"preview": "---\ntitle: Config\ndescription: Configure the sitemap module.\n---\n\n## `enabled`\n\n- Type: `boolean`{lang=\"ts\"}\n- Default: "
},
{
"path": "docs/content/4.api/1.nuxt-hooks.md",
"chars": 999,
"preview": "---\ntitle: Nuxt Hooks\ndescription: Build-time Nuxt hooks provided by @nuxtjs/sitemap.\n---\n\n## `'sitemap:prerender:done'`"
},
{
"path": "docs/content/5.nitro-api/nitro-hooks.md",
"chars": 6398,
"preview": "---\ntitle: Nitro Hooks\ndescription: Learn how to use Nitro Hooks to customize your sitemap entries.\nrelatedPages:\n - pa"
},
{
"path": "docs/content/5.releases/3.v8.md",
"chars": 3548,
"preview": "---\nnavigation:\n title: v8.0.0\ntitle: Nuxt Sitemap v8.0.0\ndescription: Release notes for v8.0.0 of Nuxt Sitemap.\n---\n\n#"
},
{
"path": "docs/content/5.releases/4.v7.md",
"chars": 1850,
"preview": "---\nnavigation:\n title: v7.0.0\ntitle: Nuxt Sitemap v7.0.0\ndescription: Release notes for v7.0.0 of Nuxt Sitemap.\n---\n\n#"
},
{
"path": "docs/content/5.releases/5.v6.md",
"chars": 4997,
"preview": "---\nnavigation:\n title: v6.0.0\ntitle: Nuxt Sitemap v6.0.0\ndescription: Release notes for v6.0.0 of Nuxt Sitemap.\n---\n\n#"
},
{
"path": "docs/content/5.releases/6.v5.md",
"chars": 1902,
"preview": "---\nnavigation:\n title: v5.0.0\ntitle: Nuxt Sitemap v5.0.0\ndescription: Release notes for v5.0.0 of Nuxt Sitemap.\n---\n\n#"
},
{
"path": "docs/content/5.releases/7.v4.md",
"chars": 5247,
"preview": "---\nnavigation:\n title: v4.0.0\ntitle: Nuxt Sitemap v4.0.0\ndescription: Release notes for v4.0.0 of Nuxt Sitemap.\n---\n\n#"
},
{
"path": "docs/content/5.releases/8.v3.md",
"chars": 3974,
"preview": "---\ntitle: v3.0.0\ndescription: Release notes for v3.0.0.\n---\n\n## Features :rocket:\n\n### 🤝 Stable I18n Integration\n\nFully"
},
{
"path": "eslint.config.mjs",
"chars": 1078,
"preview": "import antfu from '@antfu/eslint-config'\nimport harlanzw from 'eslint-plugin-harlanzw'\n\nexport default antfu(\n {\n ty"
},
{
"path": "examples/basic/app/app.vue",
"chars": 357,
"preview": "<template>\n <div>\n <h1>Sitemap Basic Example</h1>\n <nav>\n <NuxtLink to=\"/\">\n Home\n </NuxtLink>\n "
},
{
"path": "examples/basic/app/pages/about.vue",
"chars": 94,
"preview": "<template>\n <div>\n <h2>About</h2>\n <p>This is the about page.</p>\n </div>\n</template>\n"
},
{
"path": "examples/basic/app/pages/contact.vue",
"chars": 98,
"preview": "<template>\n <div>\n <h2>Contact</h2>\n <p>This is the contact page.</p>\n </div>\n</template>\n"
},
{
"path": "examples/basic/app/pages/index.vue",
"chars": 107,
"preview": "<template>\n <div>\n <h2>Home</h2>\n <p>Welcome to the basic sitemap example.</p>\n </div>\n</template>\n"
},
{
"path": "examples/basic/nuxt.config.ts",
"chars": 153,
"preview": "export default defineNuxtConfig({\n modules: ['@nuxtjs/sitemap'],\n\n site: {\n url: 'https://example.com',\n },\n\n com"
},
{
"path": "examples/basic/package.json",
"chars": 185,
"preview": "{\n \"name\": \"nuxtjs-sitemap-basic-example\",\n \"type\": \"module\",\n \"private\": true,\n \"dependencies\": {\n \"@nuxtjs/site"
},
{
"path": "examples/basic/tsconfig.json",
"chars": 41,
"preview": "{\n \"extends\": \"./.nuxt/tsconfig.json\"\n}\n"
},
{
"path": "examples/dynamic-urls/app/app.vue",
"chars": 513,
"preview": "<template>\n <div>\n <h1>Sitemap Dynamic URLs Example</h1>\n <nav>\n <NuxtLink to=\"/\">\n Home\n </Nuxt"
},
{
"path": "examples/dynamic-urls/app/pages/blog/[slug].vue",
"chars": 148,
"preview": "<template>\n <div>\n <h2>Blog Post: {{ $route.params.slug }}</h2>\n <p>This is a dynamically generated blog post pag"
},
{
"path": "examples/dynamic-urls/app/pages/index.vue",
"chars": 114,
"preview": "<template>\n <div>\n <h2>Home</h2>\n <p>Welcome to the dynamic URLs sitemap example.</p>\n </div>\n</template>\n"
},
{
"path": "examples/dynamic-urls/nuxt.config.ts",
"chars": 153,
"preview": "export default defineNuxtConfig({\n modules: ['@nuxtjs/sitemap'],\n\n site: {\n url: 'https://example.com',\n },\n\n com"
},
{
"path": "examples/dynamic-urls/package.json",
"chars": 192,
"preview": "{\n \"name\": \"nuxtjs-sitemap-dynamic-urls-example\",\n \"type\": \"module\",\n \"private\": true,\n \"dependencies\": {\n \"@nuxt"
},
{
"path": "examples/dynamic-urls/server/api/_sitemap-urls.ts",
"chars": 217,
"preview": "export default defineSitemapEventHandler(() => {\n return [\n { loc: '/blog/post-1', lastmod: new Date() },\n { loc:"
},
{
"path": "examples/dynamic-urls/tsconfig.json",
"chars": 41,
"preview": "{\n \"extends\": \"./.nuxt/tsconfig.json\"\n}\n"
},
{
"path": "examples/i18n/app/app.vue",
"chars": 439,
"preview": "<template>\n <div>\n <h1>Sitemap i18n Example</h1>\n <nav>\n <NuxtLink to=\"/\">\n Home\n </NuxtLink>\n "
},
{
"path": "examples/i18n/app/pages/about.vue",
"chars": 94,
"preview": "<template>\n <div>\n <h2>About</h2>\n <p>This is the about page.</p>\n </div>\n</template>\n"
},
{
"path": "examples/i18n/app/pages/contact.vue",
"chars": 98,
"preview": "<template>\n <div>\n <h2>Contact</h2>\n <p>This is the contact page.</p>\n </div>\n</template>\n"
},
{
"path": "examples/i18n/app/pages/index.vue",
"chars": 106,
"preview": "<template>\n <div>\n <h2>Home</h2>\n <p>Welcome to the i18n sitemap example.</p>\n </div>\n</template>\n"
},
{
"path": "examples/i18n/nuxt.config.ts",
"chars": 389,
"preview": "export default defineNuxtConfig({\n modules: ['@nuxtjs/sitemap', '@nuxtjs/i18n'],\n\n site: {\n url: 'https://example.c"
},
{
"path": "examples/i18n/package.json",
"chars": 215,
"preview": "{\n \"name\": \"nuxtjs-sitemap-i18n-example\",\n \"type\": \"module\",\n \"private\": true,\n \"dependencies\": {\n \"@nuxtjs/i18n\""
},
{
"path": "examples/i18n/tsconfig.json",
"chars": 41,
"preview": "{\n \"extends\": \"./.nuxt/tsconfig.json\"\n}\n"
},
{
"path": "package.json",
"chars": 3441,
"preview": "{\n \"name\": \"@nuxtjs/sitemap\",\n \"type\": \"module\",\n \"version\": \"8.0.15\",\n \"packageManager\": \"pnpm@10.33.2\",\n \"descrip"
},
{
"path": "patches/@nuxtjs__mdc.patch",
"chars": 743,
"preview": "diff --git a/dist/module.mjs b/dist/module.mjs\nindex e028b1a7fba50c413a0e5e40fd545d998917620a..a74d8a45a98dced151f1fc141"
},
{
"path": "playground/.nuxtrc",
"chars": 24,
"preview": "imports.autoImport=true\n"
},
{
"path": "playground/app.vue",
"chars": 1510,
"preview": "<script setup lang=\"ts\">\nimport { useI18n, useSiteConfig } from '#imports'\n\nconst i18n = useI18n()\nconst locale = i18n.l"
},
{
"path": "playground/assets/css/main.css",
"chars": 43,
"preview": "@import \"tailwindcss\";\n@import \"@nuxt/ui\";\n"
},
{
"path": "playground/content/_partial.md",
"chars": 6,
"preview": "# bar\n"
},
{
"path": "playground/content/bar.md",
"chars": 6,
"preview": "# bar\n"
},
{
"path": "playground/content/foo.md",
"chars": 40,
"preview": "---\nsitemap:\n priority: 0.5\n---\n\n# foo\n"
},
{
"path": "playground/content/posts/bar.md",
"chars": 68,
"preview": "---\nsitemap:\n loc: /blog/posts/bar\n lastmod: 2021-10-20\n---\n# bar\n"
},
{
"path": "playground/content/posts/foo.md",
"chars": 6,
"preview": "# foo\n"
},
{
"path": "playground/nuxt.config.ts",
"chars": 3432,
"preview": "import { defineNuxtConfig } from 'nuxt/config'\nimport NuxtSitemap from '../src/module'\n\nexport default defineNuxtConfig("
},
{
"path": "playground/pages/.ignored/test.vue",
"chars": 55,
"preview": "<template>\n <div>shouldn't be added</div>\n</template>\n"
},
{
"path": "playground/pages/[...slug].vue",
"chars": 57,
"preview": "<template>\n <p>{{ $route.params.slug }}</p>\n</template>\n"
},
{
"path": "playground/pages/_dir/robots.txt",
"chars": 47,
"preview": "User-agent: *\nDisallow: /blocked-by-robots-txt\n"
},
{
"path": "playground/pages/about.vue",
"chars": 254,
"preview": "<script setup lang=\"ts\">\n</script>\n\n<template>\n <div>\n About page\n <img\n src=\"https://res.cloudinary.com/dl6"
},
{
"path": "playground/pages/api/foo.vue",
"chars": 48,
"preview": "<template>\n <div>hello world</div>\n</template>\n"
},
{
"path": "playground/pages/blocked-by-robots-txt/foo.vue",
"chars": 104,
"preview": "<template>\n <div>This should be blocked by @nuxtjs/robots integration automatically.</div>\n</template>\n"
},
{
"path": "playground/pages/blog/[id].vue",
"chars": 48,
"preview": "<template>\n <div>Hello world</div>\n</template>\n"
},
{
"path": "playground/pages/blog/categories.vue",
"chars": 41,
"preview": "<template>\n <div>temp</div>\n</template>\n"
},
{
"path": "playground/pages/blog/index.vue",
"chars": 41,
"preview": "<template>\n <div>temp</div>\n</template>\n"
},
{
"path": "playground/pages/blog/tags/edit.vue",
"chars": 41,
"preview": "<template>\n <div>edit</div>\n</template>\n"
},
{
"path": "playground/pages/blog/tags/new.vue",
"chars": 40,
"preview": "<template>\n <div>new</div>\n</template>\n"
},
{
"path": "playground/pages/blog/tags.vue",
"chars": 41,
"preview": "<template>\n <div>temp</div>\n</template>\n"
},
{
"path": "playground/pages/blog.vue",
"chars": 41,
"preview": "<template>\n <div>temp</div>\n</template>\n"
},
{
"path": "playground/pages/foo.bar.vue",
"chars": 44,
"preview": "<template>\n <div>foo.bar</div>\n</template>\n"
},
{
"path": "playground/pages/hidden-path-but-in-sitemap/index.vue",
"chars": 64,
"preview": "<template>\n <div>\n <p>You found me</p>\n </div>\n</template>\n"
},
{
"path": "playground/pages/hide-me.vue",
"chars": 44,
"preview": "<template>\n <div>hide-me</div>\n</template>\n"
},
{
"path": "playground/pages/ignore-foo.vue",
"chars": 40,
"preview": "<template>\n <div>foo</div>\n</template>\n"
},
{
"path": "playground/pages/index.vue",
"chars": 916,
"preview": "<script setup lang=\"ts\">\n// does not work (yet?)\ndefinePageMeta({\n sitemap: {\n lastmod: 'broken',\n changefreq: 'f"
},
{
"path": "playground/pages/new-page.vue",
"chars": 53,
"preview": "<template>\n <div>\n New page\n </div>\n</template>\n"
},
{
"path": "playground/pages/prerender-video.vue",
"chars": 1659,
"preview": "<script setup lang=\"ts\"></script>\n\n<template>\n <div>\n Pre-render Video Discovery Page\n\n <!-- Control Video with s"
},
{
"path": "playground/pages/prerender.vue",
"chars": 61914,
"preview": "<script setup lang=\"ts\"></script>\n\n<template>\n <div>\n Pre-render Image Discovery Page\n\n <!-- Control Image, shoul"
},
{
"path": "playground/pages/secret.vue",
"chars": 132,
"preview": "<script setup lang=\"ts\">\n</script>\n\n<template>\n <div>\n <div>\n Secret page, not for robots.\n </div>\n </div>\n"
},
{
"path": "playground/pages/users-[group]/[id].vue",
"chars": 48,
"preview": "<template>\n <div>Hello world</div>\n</template>\n"
},
{
"path": "playground/pages/users-[group]/index.vue",
"chars": 48,
"preview": "<template>\n <div>Hello world</div>\n</template>\n"
},
{
"path": "playground/server/api/_sitemap-urls.ts",
"chars": 313,
"preview": "import { defineSitemapEventHandler } from '#imports'\n\nexport default defineSitemapEventHandler(() => {\n const posts = A"
},
{
"path": "playground/server/api/fetch.ts",
"chars": 254,
"preview": "import type { asSitemapUrl } from '#imports'\nimport { defineSitemapEventHandler } from '#imports'\n\nexport default define"
},
{
"path": "playground/server/api/multi-sitemap-sources/bar.ts",
"chars": 244,
"preview": "import { defineSitemapEventHandler } from '#imports'\n\nexport default defineSitemapEventHandler(() => {\n const posts = A"
},
{
"path": "playground/server/api/multi-sitemap-sources/foo.ts",
"chars": 244,
"preview": "import { defineSitemapEventHandler } from '#imports'\n\nexport default defineSitemapEventHandler(() => {\n const posts = A"
},
{
"path": "playground/server/api/prerendered.ts",
"chars": 116,
"preview": "import { defineEventHandler } from 'h3'\n\nexport default defineEventHandler(async () => {\n return { foo: 'bar' }\n})\n"
},
{
"path": "playground/server/api/sitemap-bar.ts",
"chars": 163,
"preview": "import { defineSitemapEventHandler } from '#imports'\n\nexport default defineSitemapEventHandler(() => {\n return [\n '/"
},
{
"path": "playground/server/api/sitemap-foo.ts",
"chars": 163,
"preview": "import { defineSitemapEventHandler } from '#imports'\n\nexport default defineSitemapEventHandler(() => {\n return [\n '/"
},
{
"path": "playground/server/api/sitemap-urls-to-be-confumsed-by-fetch.ts",
"chars": 358,
"preview": "import { withLeadingSlash } from 'ufo'\nimport { defineEventHandler } from '#imports'\n\nexport default defineEventHandler("
},
{
"path": "playground/server/plugins/sitemap.ts",
"chars": 395,
"preview": "import { defineNitroPlugin } from '#imports'\n\nexport default defineNitroPlugin((nitroApp) => {\n nitroApp.hooks.hook('si"
},
{
"path": "playground/server/routes/__sitemap.ts",
"chars": 245,
"preview": "import { defineSitemapEventHandler } from '#imports'\n\nexport default defineSitemapEventHandler(() => {\n const posts = A"
},
{
"path": "playground/server/tsconfig.json",
"chars": 96,
"preview": "{\n // https://v3.nuxtjs.org/concepts/typescript\n \"extends\": \"../.nuxt/tsconfig.server.json\"\n}\n"
},
{
"path": "playground/tsconfig.json",
"chars": 41,
"preview": "{\n \"extends\": \"./.nuxt/tsconfig.json\"\n}\n"
},
{
"path": "pnpm-workspace.yaml",
"chars": 1421,
"preview": "shellEmulator: true\n\ntrustPolicy: no-downgrade\n\ntrustPolicyExclude:\n - chokidar\n - semver\npackages:\n - devtools\n - t"
},
{
"path": "src/content.ts",
"chars": 5947,
"preview": "import type { Collection, PageCollectionItemBase } from '@nuxt/content'\nimport type { TypeOf } from 'zod'\nimport { creat"
},
{
"path": "src/devtools.ts",
"chars": 516,
"preview": "import type { Resolver } from '@nuxt/kit'\nimport type { Nuxt } from 'nuxt/schema'\nimport type { ModuleOptions } from './"
},
{
"path": "src/module.ts",
"chars": 47441,
"preview": "import type { FileAfterParseHook } from '@nuxt/content'\nimport type { NitroRouteConfig } from 'nitropack/types'\nimport t"
},
{
"path": "src/prerender.ts",
"chars": 8282,
"preview": "import type { Nuxt } from '@nuxt/schema'\nimport type { ConsolaInstance } from 'consola'\nimport type { Nitro, PrerenderRo"
},
{
"path": "src/runtime/server/composables/asSitemapUrl.ts",
"chars": 178,
"preview": "import type { SitemapUrlInput } from '../../types'\n\nexport function asSitemapUrl(url: SitemapUrlInput | Record<string, a"
},
{
"path": "src/runtime/server/composables/defineSitemapEventHandler.ts",
"chars": 309,
"preview": "import type { EventHandlerRequest, EventHandlerResponse } from 'h3'\nimport type { SitemapUrlInput } from '../../types'\ni"
},
{
"path": "src/runtime/server/content-compat.ts",
"chars": 159,
"preview": "// @ts-expect-error untyped\nimport { queryCollectionWithEvent } from '#sitemap/content-v3-nitro-path'\n\nexport const quer"
},
{
"path": "src/runtime/server/kit.ts",
"chars": 1022,
"preview": "import type { NitroRouteRules } from 'nitropack'\nimport { defu } from 'defu'\nimport { useRuntimeConfig } from 'nitropack"
},
{
"path": "src/runtime/server/plugins/compression.ts",
"chars": 1023,
"preview": "import type { H3Event } from 'h3'\nimport { getRequestHeader, setResponseHeader } from 'h3'\nimport { defineNitroPlugin } "
},
{
"path": "src/runtime/server/plugins/nuxt-content-v2.ts",
"chars": 2623,
"preview": "import type { NitroApp } from 'nitropack/types'\nimport type { SitemapUrl } from '../../types'\nimport { defu } from 'defu"
},
{
"path": "src/runtime/server/plugins/warm-up.ts",
"chars": 2199,
"preview": "import { defineNitroPlugin } from 'nitropack/runtime'\nimport { joinURL, withLeadingSlash } from 'ufo'\nimport { useSitema"
},
{
"path": "src/runtime/server/robots-polyfill/getPathRobotConfig.ts",
"chars": 154,
"preview": "import type { H3Event } from 'h3'\n\nexport function getPathRobotConfig(_e: H3Event, _options: any) {\n return { indexable"
},
{
"path": "src/runtime/server/routes/__sitemap__/debug-production.ts",
"chars": 3345,
"preview": "import type { SitemapWarning } from '@nuxtjs/sitemap/utils'\nimport { isSitemapIndex, parseSitemapIndex, parseSitemapXml "
},
{
"path": "src/runtime/server/routes/__sitemap__/debug.ts",
"chars": 1903,
"preview": "import type { SitemapDefinition, SitemapSourceResolved } from '../../../types'\nimport { defineEventHandler } from 'h3'\ni"
},
{
"path": "src/runtime/server/routes/__sitemap__/nuxt-content-urls-v2.ts",
"chars": 373,
"preview": "import { defineEventHandler } from 'h3'\n// @ts-expect-error alias module\nimport { serverQueryContent } from '#content/se"
},
{
"path": "src/runtime/server/routes/__sitemap__/nuxt-content-urls-v3.ts",
"chars": 2227,
"preview": "import { queryCollection } from '@nuxt/content/server'\nimport { defineEventHandler } from 'h3'\nimport manifest from '#co"
},
{
"path": "src/runtime/server/routes/__zero-runtime/sitemap/[sitemap].xml.ts",
"chars": 407,
"preview": "import { createError, defineEventHandler } from 'h3'\n\nexport default defineEventHandler(async (e) => {\n if (import.meta"
},
{
"path": "src/runtime/server/routes/__zero-runtime/sitemap.xml.ts",
"chars": 394,
"preview": "import { createError, defineEventHandler } from 'h3'\n\nexport default defineEventHandler(async (e) => {\n if (import.meta"
},
{
"path": "src/runtime/server/routes/__zero-runtime/sitemap_index.xml.ts",
"chars": 404,
"preview": "import { createError, defineEventHandler } from 'h3'\n\nexport default defineEventHandler(async (e) => {\n if (import.meta"
},
{
"path": "src/runtime/server/routes/sitemap/[sitemap].xml.ts",
"chars": 179,
"preview": "import { defineEventHandler } from 'h3'\nimport { sitemapChildXmlEventHandler } from '../../sitemap/event-handlers'\n\nexpo"
},
{
"path": "src/runtime/server/routes/sitemap.xml.ts",
"chars": 166,
"preview": "import { defineEventHandler } from 'h3'\nimport { sitemapXmlEventHandler } from '../sitemap/event-handlers'\n\nexport defau"
},
{
"path": "src/runtime/server/routes/sitemap.xsl.ts",
"chars": 18374,
"preview": "import { defineEventHandler, getHeader, getQuery as h3GetQuery, setHeader } from 'h3'\nimport { getQuery, parseURL, withQ"
},
{
"path": "src/runtime/server/routes/sitemap_index.xml.ts",
"chars": 176,
"preview": "import { defineEventHandler } from 'h3'\nimport { sitemapIndexXmlEventHandler } from '../sitemap/event-handlers'\n\nexport "
},
{
"path": "src/runtime/server/sitemap/builder/sitemap-index.ts",
"chars": 8089,
"preview": "import type { H3Event } from 'h3'\nimport type { NitroApp } from 'nitropack/types'\nimport type {\n ModuleRuntimeConfig,\n "
},
{
"path": "src/runtime/server/sitemap/builder/sitemap.ts",
"chars": 16343,
"preview": "import type { H3Event } from 'h3'\nimport type { NitroApp } from 'nitropack/types'\nimport type {\n AlternativeEntry,\n Au"
},
{
"path": "src/runtime/server/sitemap/builder/xml.ts",
"chars": 7844,
"preview": "import type { ModuleRuntimeConfig, NitroUrlResolvers, ResolvedSitemapUrl } from '../../../types'\nimport { withQuery } fr"
},
{
"path": "src/runtime/server/sitemap/event-handlers.ts",
"chars": 5049,
"preview": "import type { H3Event } from 'h3'\nimport { appendHeader, createError, getRouterParam, sendRedirect, setHeader } from 'h3"
},
{
"path": "src/runtime/server/sitemap/nitro.ts",
"chars": 9717,
"preview": "import type { H3Event } from 'h3'\nimport type { NitroApp } from 'nitropack/types'\nimport type {\n ModuleRuntimeConfig,\n "
},
{
"path": "src/runtime/server/sitemap/urlset/normalise.ts",
"chars": 6976,
"preview": "import type {\n NitroUrlResolvers,\n ResolvedSitemapUrl,\n SitemapUrl,\n SitemapUrlInput,\n} from '../../../types'\nimport"
},
{
"path": "src/runtime/server/sitemap/urlset/sort.ts",
"chars": 687,
"preview": "import type {\n ResolvedSitemapUrl,\n SitemapUrlInput,\n} from '../../../types'\n\nexport function sortInPlace<T extends Si"
},
{
"path": "src/runtime/server/sitemap/urlset/sources.ts",
"chars": 6990,
"preview": "import type { H3Event } from 'h3'\nimport type { FetchError } from 'ofetch'\nimport type {\n ModuleRuntimeConfig,\n Sitema"
},
{
"path": "src/runtime/server/sitemap/utils/chunk.ts",
"chars": 2975,
"preview": "import type { ModuleRuntimeConfig, SitemapDefinition } from '../../../types'\n\nexport interface ChunkInfo {\n isChunked: "
},
{
"path": "src/runtime/server/tsconfig.json",
"chars": 55,
"preview": "{\n \"extends\": \"../../../.nuxt/tsconfig.server.json\"\n}\n"
},
{
"path": "src/runtime/server/utils.ts",
"chars": 1293,
"preview": "import type { H3Event } from 'h3'\nimport type { ModuleRuntimeConfig } from '../types'\nimport { useRuntimeConfig } from '"
},
{
"path": "src/runtime/types.ts",
"chars": 14288,
"preview": "import type { NuxtI18nOptions } from '@nuxtjs/i18n'\nimport type { H3Event } from 'h3'\nimport type { FetchOptions } from "
},
{
"path": "src/runtime/utils-pure.ts",
"chars": 4120,
"preview": "import type { FilterInput } from './types'\nimport { createConsola } from 'consola'\nimport { createDefu } from 'defu'\nimp"
},
{
"path": "src/templates.ts",
"chars": 3364,
"preview": "import { addTemplate, addTypeTemplate, hasNuxtModule } from '@nuxt/kit'\n\nexport function registerTypeTemplates() {\n con"
},
{
"path": "src/utils/index.ts",
"chars": 400,
"preview": "export type * from '../runtime/types'\nexport { parseHtmlExtractSitemapMeta } from './parseHtmlExtractSitemapMeta'\nexport"
},
{
"path": "src/utils/parseHtmlExtractSitemapMeta.ts",
"chars": 10600,
"preview": "import type { ElementNode } from 'ultrahtml'\nimport type { ResolvedSitemapUrl, SitemapUrl, VideoEntry } from '../runtime"
},
{
"path": "src/utils/parseSitemapIndex.ts",
"chars": 2062,
"preview": "import type { SitemapWarning } from './parseSitemapXml'\n\nexport interface SitemapIndexEntry {\n loc: string\n lastmod?: "
},
{
"path": "src/utils/parseSitemapXml.ts",
"chars": 17963,
"preview": "import type { AlternativeEntry, GoogleNewsEntry, ImageEntry, SitemapStrict, SitemapUrl, SitemapUrlInput, VideoEntry } fr"
},
{
"path": "src/utils-internal/filter.ts",
"chars": 989,
"preview": "import type { FilterInput } from '../runtime/types'\n\n/**\n * Check if a filter is valid, otherwise exclude it\n * @param f"
},
{
"path": "src/utils-internal/i18n.ts",
"chars": 584,
"preview": "import type { AutoI18nConfig } from 'nuxtseo-shared/i18n'\nimport type { FilterInput } from '../runtime/types'\nimport { s"
},
{
"path": "src/utils-internal/kit.ts",
"chars": 175,
"preview": "export { createNitroPromise, createPagesPromise, detectTarget, getNuxtModuleOptions, isNuxtGenerate, resolveNitroPreset,"
},
{
"path": "src/utils-internal/nuxtSitemap.ts",
"chars": 8523,
"preview": "import type { Nuxt } from '@nuxt/schema'\nimport type { ConsolaInstance } from 'consola'\nimport type { NuxtPage } from 'n"
},
{
"path": "test/bench/i18n.bench.ts",
"chars": 975,
"preview": "import type { SitemapSourceResolved } from '#sitemap'\nimport { bench, describe } from 'vitest'\nimport { resolveSitemapEn"
},
{
"path": "test/bench/normalize.bench.ts",
"chars": 577,
"preview": "import type { SitemapSourceResolved } from '#sitemap'\nimport { bench, describe } from 'vitest'\nimport { preNormalizeEntr"
},
{
"path": "test/bench/sitemap.bench.ts",
"chars": 12120,
"preview": "import type { AlternativeEntry, AutoI18nConfig, ModuleRuntimeConfig, NitroUrlResolvers, ResolvedSitemapUrl, SitemapDefin"
},
{
"path": "test/bench/xml.bench.ts",
"chars": 2504,
"preview": "import type { ResolvedSitemapUrl } from '../../src/runtime/types'\nimport { bench, describe } from 'vitest'\nimport { urls"
},
{
"path": "test/e2e/chunks/cache-headers.test.ts",
"chars": 2790,
"preview": "import { createResolver } from '@nuxt/kit'\nimport { fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, it"
},
{
"path": "test/e2e/chunks/chunk-count.test.ts",
"chars": 1081,
"preview": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, i"
},
{
"path": "test/e2e/chunks/default.ts",
"chars": 2480,
"preview": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, i"
},
{
"path": "test/e2e/chunks/generate.test.ts",
"chars": 2996,
"preview": "import { readFile } from 'node:fs/promises'\nimport { buildNuxt, createResolver, loadNuxt } from '@nuxt/kit'\nimport { des"
},
{
"path": "test/e2e/chunks/memoization.test.ts",
"chars": 1216,
"preview": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, i"
},
{
"path": "test/e2e/content-v3/default.test.ts",
"chars": 3579,
"preview": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, i"
},
{
"path": "test/e2e/content-v3/define-schema.test.ts",
"chars": 1286,
"preview": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, i"
},
{
"path": "test/e2e/content-v3/filtering.test.ts",
"chars": 962,
"preview": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, i"
},
{
"path": "test/e2e/content-v3/i18n.test.ts",
"chars": 1615,
"preview": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, i"
},
{
"path": "test/e2e/content-v3/yaml-json.test.ts",
"chars": 1632,
"preview": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, i"
},
{
"path": "test/e2e/global-setup.ts",
"chars": 514,
"preview": "import fsp from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst fixt"
},
{
"path": "test/e2e/hooks/sources-hook-simple.test.ts",
"chars": 1074,
"preview": "import { createResolver } from '@nuxt/kit'\nimport { $fetch, setup } from '@nuxt/test-utils'\nimport { describe, expect, i"
}
]
// ... and 222 more files (download for full content)
About this extraction
This page contains the full source code of the nuxt-modules/sitemap GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 422 files (842.4 KB), approximately 267.6k tokens, and a symbol index with 172 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.