Generates sitemap.xml from your SvelteKit static routes β automatically, on every build.
- β‘οΈ Designed for SvelteKit
adapter-staticwithprerenderoption (SSG) - π· TypeScript, JavaScript, CLI and Vite plugin version
- π§ Useful options for customizing your sitemap
- ποΈ Support for sitemap index for large sites (50K+ pages)
- β² π Works with Vercel and Cloudflare adapters and more...
npm install svelte-sitemap --save-dev
# yarn add svelte-sitemap --dev
# pnpm add -D svelte-sitemap
# bun add -d svelte-sitemapRequires Node.js 20+ and SvelteKit 2 or 3. The Vite plugin needs Vite 5+.
If you're using SvelteKit with Vite (which is the default), you can integrate the sitemap generation directly into the Vite build pipeline.
Add the plugin to your vite.config.ts:
// vite.config.ts
import { sveltekit } from '@sveltejs/kit/vite';
import { svelteSitemap } from 'svelte-sitemap/vite'; // <-- Add svelte-sitemap vite plugin
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [
sveltekit(),
svelteSitemap({ domain: 'https://example.com' }) // <-- Configure the plugin with your options
]
});The sitemap is generated automatically at the end of every vite build. All options are supported.
SvelteKit 3: keep your
sveltekit({ adapter: β¦ })options as they are and just addsvelteSitemap()after it. Examples in this README use plainsveltekit()for brevity.
For other setups, the following methods are still supported but are deprecated in favor of the Vite plugin.
β¨ Config file
[!WARNING] Running the generator via CLI is an alternative. We recommend migrating to the Vite plugin instead.
Create a config file svelte-sitemap.config.ts in the root of your project:
// svelte-sitemap.config.ts
import type { OptionsSvelteSitemap } from 'svelte-sitemap';
const config: OptionsSvelteSitemap = {
domain: 'https://www.example.com',
trailingSlashes: true
// ...more options below
};
export default config;Then add svelte-sitemap as a postbuild script in package.json:
{
"scripts": {
"postbuild": "npx svelte-sitemap"
}
}After every build, the sitemap is generated in your build/ folder.
β¨οΈ CLI flags (Deprecated)
[!WARNING] Passing configuration options directly as CLI flags is deprecated and will be removed in a future version. Please use the Vite plugin or a config file instead.
Pass options directly as CLI flags β no config file needed:
{
"scripts": {
"postbuild": "npx svelte-sitemap --domain https://myawesomedomain.com"
}
}See all available flags in the Options table below.
π§ JavaScript / TypeScript API
Sometimes it's useful to call the script directly from code:
// my-script.js
import { createSitemap } from 'svelte-sitemap';
createSitemap({ domain: 'https://example.com', debug: true });Run your script:
node my-script.jsOptions are defined as camelCase properties. Use them directly in your Vite plugin configuration in vite.config.ts.
The same options are also available as config file keys or CLI flags for legacy use.
| Config key | CLI flag | Description | Default | Example |
|---|---|---|---|---|
domain |
--domain, -d |
Your domain [required] | - | domain: 'https://mydomain.com' |
outDir |
--out-dir, -o |
Custom build folder | build |
outDir: 'dist' |
additional |
--additional, -a |
Additional pages outside of SvelteKit | - | additional: ['my-page', 'my-second-page'] |
ignore |
--ignore, -i |
Ignore files or folders (glob patterns) | [] |
ignore: ['**/admin/**', 'my-secret-page'] |
trailingSlashes |
--trailing-slashes, -t |
Add trailing slashes | false |
trailingSlashes: true |
resetTime |
--reset-time, -r |
Set lastModified time to now | false |
resetTime: true |
changeFreq |
--change-freq, -c |
Set change frequency: always, hourly, daily, weekly, monthly, yearly, never |
- | changeFreq: 'daily' |
debug |
--debug |
Show some useful logs | - | debug: true |
transform |
- | Customize or exclude each page, see Transform | - | transform: (config, path) => ({ priority: 0.8 }) |
| - | --help, -h |
Display usage info | - | - |
| - | --version, -v |
Show version | - | - |
The transform option gives you full control over each sitemap entry. It is called for every page with your options (config) and the page path, and returns the fields you want to change.
This is useful for setting per-page priority, changefreq or lastmod, or adding alternateRefs for multilingual sites.
// vite.config.ts
import { sveltekit } from '@sveltejs/kit/vite';
import { svelteSitemap } from 'svelte-sitemap/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [
sveltekit(),
svelteSitemap({
domain: 'https://example.com',
changeFreq: 'monthly',
transform: (config, path) => {
if (path === '/') {
return { priority: 1.0, changefreq: 'daily' };
}
if (path.startsWith('/blog')) {
return { priority: 0.8, changefreq: 'weekly' };
}
// Other pages keep the defaults
}
})
]
});How it works:
pathis the page path without the domain, e.g./,/aboutor/blog/my-post(/about/withtrailingSlashes: true). Pages fromadditionalare passed too.- Returned fields are merged with the defaults (
changeFreq,lastmodfromresetTime), so return only what you want to change. - Return nothing (
undefined) to keep the page as it is, ornullto exclude it from the sitemap. transformcan beasync, so you can fetch data (e.g. from your CMS) before returning.- It works in the Vite plugin, the config file and the API. There is no CLI flag for it.
Fields you can return:
| Field | Description | Example |
|---|---|---|
loc |
Page URL. Defaults to the page URL. A relative path (e.g. /about) is prefixed with domain |
loc: '/about-us' |
lastmod |
Last modification date | lastmod: '2026-10-06' |
changefreq |
Change frequency, overrides changeFreq |
changefreq: 'weekly' |
priority |
Priority from 0.0 to 1.0 |
priority: 0.8 |
alternateRefs |
Language versions of the page, see below | alternateRefs: [{ href: '...', hreflang: 'es' }] |
Return null to exclude a page from the sitemap. For fixed paths, the ignore option is simpler. transform is handy when the decision needs some logic:
svelteSitemap({
domain: 'https://example.com',
transform: async (config, path) => {
const drafts = await getDraftSlugs();
if (drafts.some((slug) => path === `/blog/${slug}`)) {
return null;
}
}
});Use alternateRefs to add <xhtml:link rel="alternate" /> entries for each language version of a page. The xmlns:xhtml namespace is added to the sitemap automatically, only when some page has alternateRefs.
svelteSitemap({
domain: 'https://example.com',
transform: (config, path) => ({
alternateRefs: [
{ href: `${config.domain}${path}`, hreflang: 'en' },
{ href: `https://es.example.com${path}`, hreflang: 'es' },
{ href: `https://fr.example.com${path}`, hreflang: 'fr' }
]
})
});This produces (for the /about page):
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">
<url>
<loc>https://example.com/about</loc>
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/about" />
<xhtml:link rel="alternate" hreflang="es" href="https://es.example.com/about" />
<xhtml:link rel="alternate" hreflang="fr" href="https://fr.example.com/about" />
</url>
</urlset>Tip: Following Google's guidelines, each URL should include an alternate link pointing to itself as well.
Migrating from the CLI or config file to the Vite plugin is quick and straightforward:
-
Remove
svelte-sitemapfrompackage.jsonscripts:{ "scripts": { - "postbuild": "npx svelte-sitemap" } } -
Copy options from your config file (e.g.,
svelte-sitemap.config.ts) if you have one, and then delete it. -
Register the plugin in
vite.config.ts: ImportsvelteSitemapand configure your options directly inside the plugin. The options object is 100% compatible, so you can copy and paste your configuration directly intosvelteSitemap({...}):// vite.config.ts import { sveltekit } from '@sveltejs/kit/vite'; import { svelteSitemap } from 'svelte-sitemap/vite'; import { defineConfig } from 'vite'; export default defineConfig({ plugins: [ sveltekit(), svelteSitemap({ domain: 'https://example.com' // Paste your options object from svelte-sitemap.config.ts here. // Note: If migrating from CLI flags, convert kebab-case flags to camelCase options: // e.g. --ignore -> ignore: ['**/admin/**'] // --out-dir -> outDir: 'dist' }) ] });
This repo ships an Agent Skill that teaches coding agents (Claude Code, Cursor, Codex and others) how to set up svelte-sitemap: it checks your SvelteKit version and adapter, configures the plugin and verifies the generated sitemap.
npx skills add bartholomej/svelte-sitemapThen ask your agent, e.g. "Add a sitemap to my SvelteKit app".
Use ignore with glob patterns. For example, to ignore all admin folders and one specific page:
// vite.config.ts
import { sveltekit } from '@sveltejs/kit/vite';
import { svelteSitemap } from 'svelte-sitemap/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [
sveltekit(),
svelteSitemap({
domain: 'https://www.example.com',
ignore: ['pages/my-secret-page', '**/admin/**']
})
]
});If you're using adapter-vercel, the output directory is different from the default build/:
// vite.config.ts
import { sveltekit } from '@sveltejs/kit/vite';
import { svelteSitemap } from 'svelte-sitemap/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [
sveltekit(),
svelteSitemap({
domain: 'https://www.example.com',
outDir: '.vercel/output/static'
})
]
});Or check out other solutions and join the discussion.
@sveltejs/adapter-cloudflare writes prerendered pages to .svelte-kit/cloudflare, so set outDir to it and ignore the 404.html page the adapter creates:
// vite.config.ts
import { sveltekit } from '@sveltejs/kit/vite';
import { svelteSitemap } from 'svelte-sitemap/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [
sveltekit(),
svelteSitemap({
domain: 'https://www.example.com',
outDir: '.svelte-kit/cloudflare',
ignore: ['404.html']
})
]
});Then exclude sitemap.xml from Cloudflare's routing in svelte.config.js:
// svelte.config.js
import adapter from '@sveltejs/adapter-cloudflare';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
adapter: adapter({ routes: { include: ['/*'], exclude: ['<all>', '/sitemap.xml'] } })
}
};
export default config;SvelteKit 3 no longer uses
svelte.config.js. Pass the adapter to the Vite plugin instead:sveltekit({ adapter: adapter({ routes: { β¦ } }) }).
Γ Folder 'build/' doesn't exist. Make sure you are using this library as 'postbuild'
so 'build/' folder was successfully created before running this script.
Make sure the output folder exists. If your build outputs to a different folder than build/, use the outDir option in your config file.
Γ There is no static html file in your 'build/' folder.
Are you sure you are using Svelte adapter-static with prerender option?
This library is intended for adapter-static with the prerender option (SSG). If there are no static HTML files in your build folder, this library won't work for you :'(
Give a βοΈ if this project helped you!
Or if you are brave enough consider making a donation for some πΊ or π΅ ;)
I DO NOT STORE ANY DATA. PERIOD.
I physically can't. I have nowhere to store it. I don't even have a server database to store it. So even if Justin Bieber asked nicely to see your data, I wouldn't have anything to show him.
That's why, with this library, what happens on your device stays on your device till disappear.
I welcome you to customize this according to your needs ;)
Pull requests for any improvements would be great!
Feel free to check issues page.
git clone git@github.com:bartholomej/svelte-sitemap.git
cd svelte-sitemap
yarn
yarn startYou can find and modify it in ./demo.ts file
yarn demo- svelte-sitemap is a workaround for this official SvelteKit issue
- Brand new version is inspired by Richard's article
- Thanks to @auderer because his issue changed the direction of this library
Copyright Β© 2026 Lukas Bartak
Proudly powered by nature π», wind π¨, tea π΅ and beer πΊ ;)
All contents are licensed under the MIT license.