By SitemapFixer Team
Updated April 2026

SvelteKit Sitemap: Generate a Dynamic XML Sitemap

Validate your SvelteKit sitemap is error-free before submitting to GoogleCheck My Sitemap Free

How SvelteKit Handles Routing (No Built-in Sitemap)

SvelteKit uses a file-based routing system where every file inside src/routes/ becomes a URL. A file at src/routes/blog/[slug]/+page.svelte maps to /blog/your-post-slug. This is powerful, but unlike some frameworks, SvelteKit does not automatically generate an XML sitemap. You have to build it yourself — which gives you full control over what gets included.

The standard approach is to create a server-side endpoint that generates and serves the sitemap XML. SvelteKit's file naming convention for endpoints uses +server.js (or +server.ts for TypeScript). By placing this file at src/routes/sitemap.xml/+server.js, SvelteKit routes requests to /sitemap.xml to your handler, which returns the XML response directly.

Creating the +server.js Endpoint

Create the directory src/routes/sitemap.xml/ and add a +server.js file inside it. The GET handler must return a Response object with the correct Content-Type: application/xml header — otherwise Google Search Console and most sitemap validators will reject it.

The example below combines hardcoded static routes with dynamic blog posts fetched from an API or database. Adjust the fetch call to point at your actual data source (a headless CMS, a database query, a local file system, etc.).

// src/routes/sitemap.xml/+server.js const SITE_URL = 'https://sitemapfixer.com'; const staticRoutes = [ { url: '/', changefreq: 'daily', priority: '1.0' }, { url: '/about', changefreq: 'monthly', priority: '0.7' }, { url: '/blog', changefreq: 'weekly', priority: '0.8' }, { url: '/contact', changefreq: 'monthly', priority: '0.5' }, ]; export async function GET({ fetch }) { // Fetch dynamic pages from your CMS or DB const res = await fetch('/api/posts'); // your internal API const posts = await res.json(); const dynamicRoutes = posts.map((post) => ({ url: `/blog/${post.slug}`, lastmod: new Date(post.updatedAt).toISOString().split('T')[0], changefreq: 'weekly', priority: '0.7', })); const allRoutes = [...staticRoutes, ...dynamicRoutes]; const xml = `<?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> ${allRoutes .map( (route) => ` <url> <loc>${SITE_URL}${route.url}</loc> ${route.lastmod ? `<lastmod>${route.lastmod}</lastmod>` : ''} <changefreq>${route.changefreq}</changefreq> <priority>${route.priority}</priority> </url>` ) .join('\n')} </urlset>`; return new Response(xml, { headers: { 'Content-Type': 'application/xml', 'Cache-Control': 'max-age=0, s-maxage=3600', }, }); }

Adding lastmod, changefreq, and priority Correctly

The lastmod field is the most valuable optional element — Google uses it to prioritize re-crawling. Always derive lastmod from a real data source: the updatedAt timestamp from your CMS, the file's modification date, or a build-time timestamp. Never hard-code a static date across all pages — Google detects this pattern and ignores it.

changefreq and priority are hints that Google largely ignores in practice. Set them to reasonable values but don't spend time fine-tuning them. More important is that every URL in your sitemap returns a 200 OK status and is not blocked by your robots.txt. Use priority: 1.0 only for your homepage, and scale down from there.

Handling Multilingual Routes with hreflang

If your SvelteKit app has multiple language variants (e.g., /en/blog/post and /fr/blog/post), you need to tell Google about the relationship. The sitemap can include alternate language annotations using the xhtml:link extension. Add the namespace declaration to your urlset and include an alternate entry for each language variant.

<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml"> <url> <loc>https://example.com/en/about</loc> <xhtml:link rel="alternate" hreflang="en" href="https://example.com/en/about"/> <xhtml:link rel="alternate" hreflang="fr" href="https://example.com/fr/about"/> <xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/en/about"/> </url> </urlset>

In your +server.js, generate these alternate blocks dynamically by iterating over your supported locales for each page. Keep x-default pointing to your primary language variant.

Caching and Performance Considerations

For large sites with thousands of pages, regenerating your sitemap on every request is expensive. Set the Cache-Control: s-maxage=3600 header (as shown in the example) so your hosting CDN caches the response for an hour. If you're on Vercel or Cloudflare, this request-level caching works out of the box. For self-hosted deployments, consider pre-generating the sitemap at build time using SvelteKit's prerender option.

To prerender your sitemap at build time, add this to your endpoint file: export const prerender = true;. SvelteKit will call the GET handler during the build and write the result as a static file. This eliminates runtime overhead entirely, but requires a new build whenever your content changes — suitable for static sites, less ideal for frequently-updated blogs.

Submitting Your SvelteKit Sitemap to Google Search Console

Once your endpoint is deployed, verify it's working by visiting https://yourdomain.com/sitemap.xml in your browser. You should see valid XML with all your URLs. Then go to Google Search Console, navigate to Sitemaps in the left sidebar, enter sitemap.xml in the URL field, and click Submit. Google will begin processing it within a few minutes, though full indexing can take days to weeks depending on your site's authority.

Also add your sitemap URL to your robots.txt file. In SvelteKit, create src/routes/robots.txt/+server.js and include the line Sitemap: https://yourdomain.com/sitemap.xml in the response. This ensures any crawler — not just Google — can discover your sitemap automatically.

Make Sure Your SvelteKit Sitemap Is Error-Free
Free analysis in 60 seconds
Check My Sitemap Free

Related Guides