{"_id":"@casoon/astro-sitemap","_rev":"7-df2a67d18bed6091258a3294b55ad5e9","name":"@casoon/astro-sitemap","dist-tags":{"latest":"0.2.2"},"versions":{"0.1.0":{"name":"@casoon/astro-sitemap","version":"0.1.0","keywords":["astro","astro-integration","astro-component","withastro","sitemap","sitemap-index","seo","xml"],"license":"MIT","_id":"@casoon/astro-sitemap@0.1.0","maintainers":[{"name":"jseidel","email":"joern.seidel@casoon.de"}],"homepage":"https://github.com/casoon/astro-sitemap#readme","bugs":{"url":"https://github.com/casoon/astro-sitemap/issues"},"dist":{"shasum":"ac0d9d64d5906b16bdd02d061f3dba41c2fe3818","tarball":"https://registry.npmjs.org/@casoon/astro-sitemap/-/astro-sitemap-0.1.0.tgz","fileCount":19,"integrity":"sha512-50ufFXYF8zF3DlPEAsXnYlUHEUgWNAcH1O5RECKdcXmG5BCXSjwkGbSUxnmeHDUoK1Kc/eqppKunDoGUQ6gm+Q==","signatures":[{"sig":"MEUCIDqh9HuksMqqJ5UaiDgAdpOvM5VNe9Gk7vRX7js0rvGuAiEA4Pd4cHASzoyYNadpJjU9QrBKKqbBD3TiOXC9Loxqw70=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":56535},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./rss":{"types":"./dist/rss.d.ts","import":"./dist/rss.js"}},"gitHead":"ae8ab59ea6a3ab4b4ed166fac0e3129f8f6f017f","scripts":{"test":"vitest run","build":"tsc","prepublishOnly":"npm test && npm run build"},"_npmUser":{"name":"jseidel","email":"joern.seidel@casoon.de"},"repository":{"url":"git+https://github.com/casoon/astro-sitemap.git","type":"git"},"_npmVersion":"11.6.2","description":"Astro integration that generates sitemap.xml at build time — supports dynamic sources, pattern-based rules, i18n hreflang, serialize hook, and sitemap-index chunking.","directories":{},"sideEffects":false,"_nodeVersion":"24.11.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^2.0.0","typescript":"^5.8.3","@types/node":"^22.0.0"},"peerDependencies":{"astro":">=6.0.0"},"peerDependenciesMeta":{"astro":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/astro-sitemap_0.1.0_1775894526720_0.21245502291049267","host":"s3://npm-registry-packages-npm-production"},"deprecated":"Nicht mehr gepflegt – bitte @casoon/astro-site-files verwenden (erzeugt sitemap.xml)"},"0.1.1":{"name":"@casoon/astro-sitemap","version":"0.1.1","keywords":["astro","astro-integration","astro-component","withastro","sitemap","sitemap-index","seo","xml"],"license":"MIT","_id":"@casoon/astro-sitemap@0.1.1","maintainers":[{"name":"jseidel","email":"joern.seidel@casoon.de"}],"homepage":"https://github.com/casoon/astro-sitemap#readme","bugs":{"url":"https://github.com/casoon/astro-sitemap/issues"},"dist":{"shasum":"4bfac06364ad7cd67694ddaa8762dc040ffda541","tarball":"https://registry.npmjs.org/@casoon/astro-sitemap/-/astro-sitemap-0.1.1.tgz","fileCount":19,"integrity":"sha512-1okHWAdyCdObdUXEuSLCobMDcXw/mdEJviyYMsYIceOjQWvvU0IkWWjQHWlXbcPe/SIjjkvwcJ6t0pTrohnjmg==","signatures":[{"sig":"MEUCIQCkz6fjxu3jM1IAeAmz7q2wYkruF0mc/gcHLua718CBiAIgHIbI7D61Sb/t7YtefqvBazIbb76t0yezVhd6QePQHZk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":60627},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./rss":{"types":"./dist/rss.d.ts","import":"./dist/rss.js"}},"gitHead":"ae8ab59ea6a3ab4b4ed166fac0e3129f8f6f017f","scripts":{"test":"vitest run","build":"tsc","prepublishOnly":"npm test && npm run build"},"_npmUser":{"name":"jseidel","email":"joern.seidel@casoon.de"},"repository":{"url":"git+https://github.com/casoon/astro-sitemap.git","type":"git"},"_npmVersion":"11.6.2","description":"Astro integration that generates sitemap.xml at build time — supports dynamic sources, pattern-based rules, i18n hreflang, serialize hook, and sitemap-index chunking.","directories":{},"sideEffects":false,"_nodeVersion":"24.11.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^2.0.0","typescript":"^5.8.3","@types/node":"^22.0.0"},"peerDependencies":{"astro":">=6.0.0"},"peerDependenciesMeta":{"astro":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/astro-sitemap_0.1.1_1775895336514_0.34306584563466713","host":"s3://npm-registry-packages-npm-production"},"deprecated":"Nicht mehr gepflegt – bitte @casoon/astro-site-files verwenden (erzeugt sitemap.xml)"},"0.1.2":{"name":"@casoon/astro-sitemap","version":"0.1.2","keywords":["astro","astro-integration","astro-component","withastro","sitemap","sitemap-index","seo","xml"],"license":"MIT","_id":"@casoon/astro-sitemap@0.1.2","maintainers":[{"name":"jseidel","email":"joern.seidel@casoon.de"}],"homepage":"https://github.com/casoon/astro-sitemap#readme","bugs":{"url":"https://github.com/casoon/astro-sitemap/issues"},"dist":{"shasum":"17529c0b8911ad3143a44947153b826695947ee9","tarball":"https://registry.npmjs.org/@casoon/astro-sitemap/-/astro-sitemap-0.1.2.tgz","fileCount":19,"integrity":"sha512-uI6dS+crX36tlHgu3m8zkKO202NOUkxJtuQiHQkGn6wjytRcj16HR8kSrSsbUrGYnmVc3E1FiNiEH+ZVirznrA==","signatures":[{"sig":"MEQCIDHMXK/M+xcu8sidnuxqHZbFpKcprXiriea5nE0RX002AiAseA4TQNjhvnw1snY6z3IoLIUTQPU91FZblufmKPT8Yg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":60386},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./rss":{"types":"./dist/rss.d.ts","import":"./dist/rss.js"}},"gitHead":"ae8ab59ea6a3ab4b4ed166fac0e3129f8f6f017f","scripts":{"test":"vitest run","build":"tsc","prepublishOnly":"npm test && npm run build"},"_npmUser":{"name":"jseidel","email":"joern.seidel@casoon.de"},"repository":{"url":"git+https://github.com/casoon/astro-sitemap.git","type":"git"},"_npmVersion":"11.6.2","description":"Astro integration that generates sitemap.xml at build time — supports dynamic sources, pattern-based rules, i18n hreflang, serialize hook, and sitemap-index chunking.","directories":{},"sideEffects":false,"_nodeVersion":"24.11.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^2.0.0","typescript":"^5.8.3","@types/node":"^22.0.0"},"peerDependencies":{"astro":">=6.0.0"},"peerDependenciesMeta":{"astro":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/astro-sitemap_0.1.2_1775895674220_0.7782638951481082","host":"s3://npm-registry-packages-npm-production"},"deprecated":"Nicht mehr gepflegt – bitte @casoon/astro-site-files verwenden (erzeugt sitemap.xml)"},"0.2.0":{"name":"@casoon/astro-sitemap","version":"0.2.0","keywords":["astro","astro-integration","astro-component","withastro","sitemap","sitemap-index","seo","xml"],"license":"MIT","_id":"@casoon/astro-sitemap@0.2.0","maintainers":[{"name":"jseidel","email":"joern.seidel@casoon.de"}],"homepage":"https://github.com/casoon/astro-sitemap#readme","bugs":{"url":"https://github.com/casoon/astro-sitemap/issues"},"dist":{"shasum":"74ea0c14b7a6ffbedbe5af61dd515dc240f37db8","tarball":"https://registry.npmjs.org/@casoon/astro-sitemap/-/astro-sitemap-0.2.0.tgz","fileCount":19,"integrity":"sha512-+o234DA7RJyJCBIePS0ZEL/ylT6G++uRdc7fyqoJIhzoWiyEzHfL2IbQKs5OyynhMl1TsTU0p51ZOJobr2zuLA==","signatures":[{"sig":"MEQCIAYqjhQZjcJOy0w4K/qFYsbRcJpiIyS1ytxt034iJCFmAiAr1I60Wdd6RnxIjOrXTDP3KPWdJv1eUhN94VHs2SbQvg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":64735},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./rss":{"types":"./dist/rss.d.ts","import":"./dist/rss.js"}},"gitHead":"b7e4b50912e990b9eb6cecccbb371fb491dbc6a5","scripts":{"test":"vitest run","build":"tsc","prepublishOnly":"npm test && npm run build"},"_npmUser":{"name":"jseidel","email":"joern.seidel@casoon.de"},"repository":{"url":"git+https://github.com/casoon/astro-sitemap.git","type":"git"},"_npmVersion":"11.6.2","description":"Astro integration that generates sitemap.xml at build time — supports dynamic sources, pattern-based rules, i18n hreflang, serialize hook, and sitemap-index chunking.","directories":{},"sideEffects":false,"_nodeVersion":"24.11.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^2.0.0","typescript":"^5.8.3","@types/node":"^22.0.0"},"peerDependencies":{"astro":">=6.0.0"},"peerDependenciesMeta":{"astro":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/astro-sitemap_0.2.0_1775901340435_0.3017713073122412","host":"s3://npm-registry-packages-npm-production"},"deprecated":"Nicht mehr gepflegt – bitte @casoon/astro-site-files verwenden (erzeugt sitemap.xml)"},"0.2.1":{"name":"@casoon/astro-sitemap","version":"0.2.1","keywords":["astro","astro-integration","astro-component","withastro","sitemap","sitemap-index","seo","xml"],"license":"MIT","_id":"@casoon/astro-sitemap@0.2.1","maintainers":[{"name":"jseidel","email":"joern.seidel@casoon.de"}],"homepage":"https://github.com/casoon/astro-sitemap#readme","bugs":{"url":"https://github.com/casoon/astro-sitemap/issues"},"dist":{"shasum":"fd82eb01cb2eba5db2d391a08db4af1ea94afaeb","tarball":"https://registry.npmjs.org/@casoon/astro-sitemap/-/astro-sitemap-0.2.1.tgz","fileCount":19,"integrity":"sha512-i7PG7qlDjOTUlpkRLlT0OuWwLkT/IY6/xbL8EoTK6u9Jqct+QiNgEpVHxBjy02qaIhzxsJvVwqR5Jng4jsVnyg==","signatures":[{"sig":"MEYCIQCYVQqRaeGjM+dZ610NEul8kFqZJ6pbeBd1P8kFEK26CgIhAIb0V82OIlYRvs+Ch8Lu7WsroZq4QEdCZNExi1LXfvbJ","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":64743},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./rss":{"types":"./dist/rss.d.ts","import":"./dist/rss.js"}},"gitHead":"b7e4b50912e990b9eb6cecccbb371fb491dbc6a5","scripts":{"test":"vitest run","build":"tsc","prepublishOnly":"npm test && npm run build"},"_npmUser":{"name":"jseidel","email":"joern.seidel@casoon.de"},"repository":{"url":"git+https://github.com/casoon/astro-sitemap.git","type":"git"},"_npmVersion":"11.6.2","description":"Astro integration that generates sitemap.xml at build time — supports dynamic sources, pattern-based rules, i18n hreflang, serialize hook, and sitemap-index chunking.","directories":{},"sideEffects":false,"_nodeVersion":"24.11.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^2.0.0","typescript":"^5.8.3","@types/node":"^22.0.0"},"peerDependencies":{"astro":">=6.0.0"},"peerDependenciesMeta":{"astro":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/astro-sitemap_0.2.1_1775901745013_0.6196513512216297","host":"s3://npm-registry-packages-npm-production"},"deprecated":"Nicht mehr gepflegt – bitte @casoon/astro-site-files verwenden (erzeugt sitemap.xml)"},"0.2.2":{"name":"@casoon/astro-sitemap","version":"0.2.2","keywords":["astro","astro-integration","astro-component","withastro","sitemap","sitemap-index","seo","xml"],"license":"MIT","_id":"@casoon/astro-sitemap@0.2.2","maintainers":[{"name":"jseidel","email":"joern.seidel@casoon.de"}],"homepage":"https://github.com/casoon/astro-sitemap#readme","bugs":{"url":"https://github.com/casoon/astro-sitemap/issues"},"dist":{"shasum":"b06c32d0b34486c461da5cea39887bb14ca5ed34","tarball":"https://registry.npmjs.org/@casoon/astro-sitemap/-/astro-sitemap-0.2.2.tgz","fileCount":19,"integrity":"sha512-vh+wXdpInlC3gbrDNOAIAjLsQh8OnI7nYTFMWHJvikjIJUcx67kqqbjS7iJE4BG01cAEPVbqp8URIERYISS/GQ==","signatures":[{"sig":"MEYCIQDjU53nFZfLDsBfKlJKYyxHxKHp5xhqX+EEwQ5FTJadZgIhAI5LO6kPCxL/k0kt3u45EAgqIiia6WNUiPlRZUFyycEm","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":65293},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./rss":{"types":"./dist/rss.d.ts","import":"./dist/rss.js"}},"gitHead":"349fdf5144e544c9471772621ded682434b061b2","scripts":{"test":"vitest run","build":"tsc","prepublishOnly":"npm test && npm run build"},"_npmUser":{"name":"jseidel","email":"joern.seidel@casoon.de"},"repository":{"url":"git+https://github.com/casoon/astro-sitemap.git","type":"git"},"_npmVersion":"11.6.2","description":"Astro integration that generates sitemap.xml at build time — supports dynamic sources, pattern-based rules, i18n hreflang, serialize hook, and sitemap-index chunking.","directories":{},"sideEffects":false,"_nodeVersion":"24.11.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^2.0.0","typescript":"^5.8.3","@types/node":"^22.0.0"},"peerDependencies":{"astro":">=6.0.0"},"peerDependenciesMeta":{"astro":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/astro-sitemap_0.2.2_1775903630804_0.6032720843968369","host":"s3://npm-registry-packages-npm-production"},"deprecated":"Nicht mehr gepflegt – bitte @casoon/astro-site-files verwenden (erzeugt sitemap.xml)"}},"time":{"created":"2026-04-11T08:02:06.607Z","modified":"2026-09-16T06:08:23.493Z","0.1.0":"2026-04-11T08:02:06.868Z","0.1.1":"2026-04-11T08:15:36.646Z","0.1.2":"2026-04-11T08:21:14.364Z","0.2.0":"2026-04-11T09:55:40.582Z","0.2.1":"2026-04-11T10:02:25.182Z","0.2.2":"2026-04-11T10:33:50.957Z"},"bugs":{"url":"https://github.com/casoon/astro-sitemap/issues"},"license":"MIT","homepage":"https://github.com/casoon/astro-sitemap#readme","keywords":["astro","astro-integration","astro-component","withastro","sitemap","sitemap-index","seo","xml"],"repository":{"url":"git+https://github.com/casoon/astro-sitemap.git","type":"git"},"description":"Astro integration that generates sitemap.xml at build time — supports dynamic sources, pattern-based rules, i18n hreflang, serialize hook, and sitemap-index chunking.","maintainers":[{"name":"jseidel","email":"joern.seidel@casoon.de"}],"readme":"# @casoon/astro-sitemap\n\n[![CI](https://github.com/casoon/astro-sitemap/actions/workflows/ci.yml/badge.svg)](https://github.com/casoon/astro-sitemap/actions/workflows/ci.yml)\n[![npm](https://img.shields.io/npm/v/@casoon/astro-sitemap)](https://www.npmjs.com/package/@casoon/astro-sitemap)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n\nAstro integration that generates `sitemap.xml` at build time.\n\n- Discovers pages via Astro's build pipeline — no `dist/` scanning\n- Dynamic URL sources (content collections, CMS, APIs)\n- Pattern-based `priority` and `changefreq` rules\n- Per-item `serialize` hook for fine-grained control\n- i18n `hreflang` link generation\n- Automatic `sitemap-index.xml` chunking for large sites\n- Built-in audit with warnings for empty sitemaps, duplicates, and invalid values\n\n**Requirements:** Astro 6+, Node 18+\n\n---\n\n## Installation\n\n```sh\nnpm install @casoon/astro-sitemap\n```\n\n## Quick Start\n\n```js\n// astro.config.mjs\nimport { defineConfig } from 'astro/config';\nimport astroSitemap from '@casoon/astro-sitemap';\n\nexport default defineConfig({\n  site: 'https://example.com',\n  integrations: [astroSitemap()],\n});\n```\n\nThis generates `dist/sitemap.xml` containing all pages discovered from the build.\n\n---\n\n## Configuration\n\n```ts\nastroSitemap({\n  // all options are optional\n  siteUrl: 'https://example.com',\n  sources: [...],\n  exclude: [...],\n  filter: (url) => true,\n  priority: [...],\n  changefreq: [...],\n  serialize: async (entry) => entry,\n  i18n: { defaultLocale: 'en', locales: { en: 'en', de: 'de-DE' } },\n  output: { mode: 'single', maxUrls: 50_000, filename: 'sitemap.xml' },\n  audit: { warnOnEmpty: true, errorOnDuplicates: false },\n  debug: false,\n})\n```\n\n### `siteUrl`\n\n```ts\nsiteUrl?: string\n```\n\nBase URL of your site. Auto-detected from `site` in `astro.config` if omitted.\n\n---\n\n### `base` (Astro config — not a plugin option)\n\nIf your site is deployed to a subdirectory, set `base` in `astro.config`. The plugin reads it automatically — no plugin configuration needed.\n\n```js\n// astro.config.mjs\nexport default defineConfig({\n  site: 'https://example.com',\n  base: '/my-app/',               // sitemap URLs become https://example.com/my-app/...\n  integrations: [astroSitemap()],\n});\n```\n\nRelative paths in `sources` entries are also prefixed with the base automatically.\n\n---\n\n### `sources`\n\n```ts\nsources?: Array<() => Promise<SitemapEntry[]>>\n```\n\nAsync functions that return additional URL entries. Use for dynamic content not generated as static pages.\n\n```js\nastroSitemap({\n  sources: [\n    async () => {\n      const { getCollection } = await import('astro:content');\n      const posts = await getCollection('blog');\n      return posts\n        .filter((p) => !p.data.draft)\n        .map((p) => ({\n          loc: `/blog/${p.id}/`,\n          lastmod: p.data.updatedAt ?? p.data.date,\n          priority: 0.7,\n        }));\n    },\n  ],\n})\n```\n\n`SitemapEntry` fields:\n\n| Field | Type | Description |\n|---|---|---|\n| `loc` | `string` | Full URL or root-relative path (e.g. `/blog/post/`) |\n| `lastmod` | `string` | ISO date string (defaults to today) |\n| `priority` | `number` | 0.0–1.0 (defaults to depth-based rule) |\n| `changefreq` | `Changefreq` | Defaults to built-in pattern rules |\n\nSource entries are merged with static pages. On duplicate `loc`, the **last entry wins** — sources listed later override earlier sources and scanned pages.\n\n> **Draft pages:** Always filter drafts in the source function — the plugin only sees URLs, not frontmatter.\n> For `src/pages/*.md` files with `draft: true`, Astro skips building them automatically (they never reach the sitemap).\n> For content collections you must filter explicitly: `getCollection('blog', ({ data }) => !data.draft)`\n\n---\n\n### `exclude`\n\n```ts\nexclude?: Array<string | RegExp>\n```\n\nURL patterns to exclude from the sitemap. String patterns match if the path equals or starts with the value.\n\n```js\nastroSitemap({\n  exclude: ['/intern/', /\\/preview\\//],\n})\n```\n\nBuilt-in exclusions always apply regardless of this option:\n`/sitemap*.xml`, `/robots.txt`, `/llms.txt`, `/rss.xml`, `/feed.xml`, `/404`, `/500`, `/_*`, `/api/*`, `/landing/*`, `/drafts/*`\n\n---\n\n### `filter`\n\n```ts\nfilter?: (url: string) => boolean\n```\n\nFunction-based URL filter. Receives the full absolute URL. Return `false` to exclude. Applied after `exclude` patterns.\n\n```js\nastroSitemap({\n  filter: (url) => !url.includes('/internal/'),\n})\n```\n\n---\n\n### `priority`\n\n```ts\npriority?: Array<{ pattern: string | RegExp; priority: number }>\n```\n\nCustom priority rules evaluated before built-in defaults. First matching rule wins.\n\n```js\nastroSitemap({\n  priority: [\n    { pattern: '/produkte/', priority: 0.9 },\n    { pattern: /\\/blog\\/\\d{4}\\//, priority: 0.6 },\n  ],\n})\n```\n\n> **Gotcha — targeting the homepage:** The string `'/'` matches **every** URL (all paths start with `/`), so it acts as a catch-all, not a homepage selector. Use the RegExp `/^\\/$/ ` to match only the root path:\n>\n> ```js\n> // Wrong — sets priority 0.9 on ALL pages:\n> { pattern: '/',      priority: 0.9 }\n>\n> // Correct — sets priority 0.9 on the homepage only:\n> { pattern: /^\\/$/, priority: 0.9 }\n> ```\n>\n> The built-in defaults already assign `/` a priority of 1.0, so you only need this when overriding the homepage to a different value.\n\nBuilt-in depth-based defaults (applied when no rule matches):\n\n| Path | Priority |\n|---|---|\n| `/` | 1.0 |\n| Depth 1 (e.g. `/about/`) | 0.9 |\n| Depth 2 (e.g. `/blog/post/`) | 0.8 |\n| Depth 3+ | 0.7 |\n\n---\n\n### `changefreq`\n\n```ts\nchangefreq?: Array<{ pattern: string | RegExp; changefreq: Changefreq }>\n```\n\nCustom changefreq rules, evaluated before built-in defaults. First matching rule wins.\n\n```js\nastroSitemap({\n  changefreq: [\n    { pattern: '/produkte/', changefreq: 'daily' },\n    { pattern: '/impressum/', changefreq: 'yearly' },\n  ],\n})\n```\n\nBuilt-in defaults: `/` and `/blog|artikel|news|posts|updates/` → `weekly`, everything else → `monthly`.\n\n---\n\n### `serialize`\n\n```ts\nserialize?: (entry: ResolvedSitemapEntry) => ResolvedSitemapEntry | undefined | Promise<...>\n```\n\nCalled for every resolved entry after deduplication. Return a modified entry to keep it, or `undefined` to drop it from the sitemap.\n\n```js\nastroSitemap({\n  serialize: async (entry) => {\n    // Drop preview pages\n    if (entry.loc.includes('/preview/')) return undefined;\n\n    // Override lastmod from a CMS\n    const lastmod = await fetchLastmod(entry.loc);\n    return { ...entry, lastmod };\n  },\n})\n```\n\n`ResolvedSitemapEntry` fields: `loc`, `lastmod`, `priority`, `changefreq`, `links` (hreflang, set by `i18n`).\n\n---\n\n### `i18n`\n\n```ts\ni18n?: {\n  defaultLocale: string;\n  locales: Record<string, string>; // locale prefix → hreflang value\n}\n```\n\nEnables `<xhtml:link rel=\"alternate\" hreflang=\"...\">` generation for multi-language sites. Pages are grouped by their path without the locale prefix; each page gets alternate links for all sibling locale variants.\n\n```js\nastroSitemap({\n  i18n: {\n    defaultLocale: 'en',\n    locales: {\n      en: 'en',\n      de: 'de-DE',\n      fr: 'fr-FR',\n    },\n  },\n})\n```\n\nGiven pages `/en/about/` and `/de/about/`, the output for `/en/about/` will include:\n\n```xml\n<url>\n  <loc>https://example.com/en/about/</loc>\n  ...\n  <xhtml:link rel=\"alternate\" hreflang=\"en\" href=\"https://example.com/en/about/\"/>\n  <xhtml:link rel=\"alternate\" hreflang=\"de-DE\" href=\"https://example.com/de/about/\"/>\n</url>\n```\n\nPages with only one locale version get no `xhtml:link` elements.\n\n---\n\n### `output`\n\n```ts\noutput?: {\n  mode?: 'single' | 'index';\n  maxUrls?: number;\n  filename?: string;\n}\n```\n\n| Option | Default | Description |\n|---|---|---|\n| `mode` | auto | `'single'` forces one file; `'index'` forces index mode; auto-selects based on URL count |\n| `maxUrls` | `50000` | URLs per chunk before switching to index mode |\n| `filename` | `'sitemap.xml'` | Output filename in single mode |\n\nWhen the URL count exceeds `maxUrls`, the plugin automatically generates `sitemap-index.xml` + numbered `sitemap-N.xml` chunks.\n\n---\n\n### `audit`\n\n```ts\naudit?: {\n  warnOnEmpty?: boolean;\n  errorOnDuplicates?: boolean;\n}\n```\n\n| Option | Default | Description |\n|---|---|---|\n| `warnOnEmpty` | `true` | Warn when sitemap has zero entries |\n| `errorOnDuplicates` | `false` | Treat duplicate URLs as errors instead of warnings |\n\n---\n\n### `debug`\n\n```ts\ndebug?: boolean\n```\n\nEnables detailed logging during the build (collected page count, source batch sizes, deduplication results).\n\n---\n\n## Recipes\n\n### Blog with content collections (static routes)\n\nIf blog posts are generated as static Astro pages (`src/pages/blog/[slug].astro`), they are **automatically discovered** from the build — no `sources` needed. Use priority/changefreq rules to tune the values:\n\n```js\n// astro.config.mjs\nastroSitemap({\n  priority: [\n    { pattern: '/blog/', priority: 0.7 },\n  ],\n  changefreq: [\n    { pattern: '/blog/', changefreq: 'monthly' },\n  ],\n})\n```\n\n---\n\n### External / headless CMS sources\n\nUse `sources` for URLs that are **not** generated as Astro pages — e.g. content from a headless CMS that you fetch via API. Do not use `import('astro:content')` inside sources, as that virtual module is not available outside the Vite build context.\n\n```js\nastroSitemap({\n  sources: [\n    async () => {\n      // Fetch from an external API\n      const res = await fetch('https://cms.example.com/api/pages');\n      const pages = await res.json();\n      return pages.map((p) => ({\n        loc: `/blog/${p.slug}/`,\n        lastmod: p.updatedAt,\n        priority: 0.7,\n        changefreq: 'monthly',\n      }));\n    },\n  ],\n})\n```\n\n---\n\n### Blog + taxonomy pages (categories, series)\n\nIf taxonomy pages (`/blog/category/[slug].astro`) are also static Astro routes, they appear automatically too. Assign different priorities per path pattern:\n\n```js\nastroSitemap({\n  priority: [\n    { pattern: '/blog/category/', priority: 0.6 },\n    { pattern: '/blog/',          priority: 0.7 },\n  ],\n  changefreq: [\n    { pattern: '/blog/category/', changefreq: 'weekly' },\n    { pattern: '/blog/',          changefreq: 'monthly' },\n  ],\n})\n```\n\n---\n\n### Exclude pages and override priorities\n\n```js\nastroSitemap({\n  exclude: ['/intern/', '/preview/', /\\/admin\\//],\n  filter: (url) => !url.includes('?'),\n  priority: [\n    { pattern: '/produkte/',  priority: 0.9 },\n    { pattern: '/blog/',      priority: 0.7 },\n    { pattern: '/impressum/', priority: 0.3 },\n  ],\n  changefreq: [\n    { pattern: '/blog/',      changefreq: 'weekly' },\n    { pattern: '/impressum/', changefreq: 'yearly' },\n  ],\n})\n```\n\n---\n\n### Override `lastmod` from a CMS\n\nUse `serialize` to enrich or drop individual entries after all sources are merged.\n\n```js\nastroSitemap({\n  serialize: async (entry) => {\n    // Drop anything not yet ready\n    if (entry.loc.includes('/draft/')) return undefined;\n\n    // Fetch accurate lastmod from your CMS\n    const lastmod = await cms.getLastmod(entry.loc);\n    return lastmod ? { ...entry, lastmod } : entry;\n  },\n})\n```\n\n`ResolvedSitemapEntry` fields: `loc`, `lastmod`, `priority`, `changefreq`, `links` (hreflang, set by `i18n`).\n\n> See the [`examples/`](./examples) directory for complete, copy-paste-ready recipes.\n\n---\n\n### Multi-language site (i18n / hreflang)\n\n```js\n// astro.config.mjs\nexport default defineConfig({\n  site: 'https://example.com',\n  i18n: {\n    defaultLocale: 'en',\n    locales: ['en', 'de', 'fr'],\n  },\n  integrations: [\n    astroSitemap({\n      i18n: {\n        defaultLocale: 'en',\n        locales: { en: 'en', de: 'de-DE', fr: 'fr-FR' },\n      },\n    }),\n  ],\n});\n```\n\nGiven pages `/en/about/`, `/de/about/`, `/fr/about/`, each URL gets `<xhtml:link rel=\"alternate\">` entries for all language variants automatically.\n\n---\n\n### RSS feed alongside sitemap\n\n```ts\n// src/pages/rss.xml.ts\nimport { createRssRoute } from '@casoon/astro-sitemap/rss';\nimport { getCollection } from 'astro:content';\n\nexport const GET = createRssRoute({\n  title: 'My Blog',\n  description: 'Latest posts',\n  language: 'en',\n  copyright: `Copyright ${new Date().getFullYear()} ACME`,\n  getItems: async (siteUrl) => {\n    const posts = await getCollection('blog', ({ data }) => !data.draft);\n    return posts\n      .sort((a, b) => +new Date(b.data.date) - +new Date(a.data.date))\n      .map((p) => ({\n        title:       p.data.title,\n        description: p.data.description,\n        pubDate:     p.data.date,\n        link:        `${siteUrl}/blog/${p.id}/`,\n        author:      p.data.author,\n        categories:  p.data.tags,\n      }));\n  },\n});\n```\n\n---\n\n## How it works\n\n1. **Config** — Captures `site`, `base`, `trailingSlash`, `build.format` from Astro config. `build.format: 'file'` and `'preserve'` automatically append `.html` to non-index page URLs\n2. **Routes** — `astro:routes:resolved` collects i18n fallback routes\n3. **Pages** — `astro:build:done` provides the list of all rendered pages directly from Astro (no filesystem scan)\n4. **Sources** — Each source function is awaited and its entries collected\n5. **Resolve** — Each entry gets `lastmod`, `priority`, `changefreq` from user rules or built-in defaults\n6. **Deduplicate** — Duplicate `loc` values are collapsed (last entry wins)\n7. **Serialize** — The `serialize` hook runs per entry if configured\n8. **i18n** — hreflang links are attached when `i18n` is configured\n9. **Audit** — Warnings/errors are emitted for empty sitemaps, duplicates, invalid priorities\n10. **Write** — Output is written as `sitemap.xml` or `sitemap-index.xml` + chunks\n\n---\n\n## Optional: RSS Feed Helper\n\nThe package ships an optional `createRssRoute` helper that generates an RSS 2.0 feed. It has no external dependencies and integrates naturally with Astro's API routes.\n\n```ts\n// src/pages/rss.xml.ts\nimport { createRssRoute } from '@casoon/astro-sitemap/rss';\nimport { getCollection } from 'astro:content';\n\nexport const GET = createRssRoute({\n  title: 'My Blog',\n  description: 'Latest posts',\n  language: 'en',\n  getItems: async (siteUrl) => {\n    const posts = await getCollection('blog', ({ data }) => !data.draft);\n    return posts.map(p => ({\n      title:   p.data.title,\n      pubDate: p.data.date,\n      link:    `${siteUrl}/blog/${p.id}/`,\n    }));\n  },\n});\n```\n\n### `createRssRoute` options\n\n| Option | Type | Description |\n|---|---|---|\n| `title` | `string` | Feed title |\n| `description` | `string` | Feed description |\n| `getItems` | `(siteUrl: string) => RssItem[] \\| Promise<RssItem[]>` | Returns feed items. Receives resolved `siteUrl` (no trailing slash) |\n| `siteUrl` | `string?` | Override base URL (auto-detected from `context.site`) |\n| `feedUrl` | `string?` | URL of this feed file — used for `<atom:link rel=\"self\">`. Defaults to `{siteUrl}/rss.xml` |\n| `language` | `string?` | Language code, e.g. `'en'` or `'de-DE'` |\n| `copyright` | `string?` | Copyright notice |\n| `managingEditor` | `string?` | Managing editor name or e-mail |\n| `feedCustomData` | `string?` | Raw XML injected inside `<channel>` |\n\n### `RssItem` fields\n\n| Field | Type | Description |\n|---|---|---|\n| `title` | `string` | Item title |\n| `pubDate` | `Date \\| string` | Publication date |\n| `link` | `string` | Full URL or root-relative path (auto-prefixed with `siteUrl`) |\n| `description` | `string?` | Item summary |\n| `author` | `string?` | Author name or e-mail |\n| `categories` | `string[]?` | Category / tag strings |\n| `customData` | `string?` | Raw XML injected inside `<item>` |\n\n---\n\n## Differences to `@astrojs/sitemap`\n\n| Feature | `@casoon/astro-sitemap` | `@astrojs/sitemap` |\n|---|---|---|\n| URL discovery | Astro `pages` array + routes hook | Astro `pages` array + routes hook |\n| URL filtering | `exclude` (patterns) + `filter` (function) | `filter` (function) |\n| Dynamic sources | `sources[]` (async functions) | `customPages` (string array) |\n| Priority | Depth-based defaults + pattern rules | Global default only |\n| Changefreq | Pattern rules + smart defaults | Global default only |\n| Per-item transform | `serialize` hook | `serialize` hook |\n| i18n / hreflang | `i18n` option | `i18n` option |\n| Audit | Warnings for empty/duplicates/invalid | None |\n| XML formatting | Indented, human-readable | Minified (via `sitemap` package) |\n| Chunked sitemaps | Auto + manual | Auto + `chunks` (named groups) |\n| XSL stylesheet | — | `xslURL` option |\n\n---\n\n## License\n\nMIT\n","readmeFilename":"README.md"}