tracking for shynet analytics when Javascript is disabled Creating sitemap for your SvelteKit project with minimal effort — Hugo Sum
Hugo Sum

Creating sitemap for your SvelteKit project with minimal effort

Sitemap is crucial for the SEO success of a website. Even though SvelteKit is designed with SEO in mind, the decision was to keep sitemap generation in the userland. I have been using SvelteKit since version pre 1.0, and I have created sitemaps for SvelteKit projects using different adapters. In this post I want to share my experience on building sitemap in SvelteKit and introduce super-sitemap to you, which provides the ideal interface and experience for building sitemap in my opinion.

Common ways of creating sitemap for SvelteKit projects

Building sitemap usually involve two steps, metadata retrieval and transformation. The process of metadata retrieval is largely shaped by how data is stored, and it is specific to a project. Projects that store metadata as local files usually use import.meta.glob function for bulk import; projects that store metadata remotely, such as in a CMS, usually collect its data with HTTP requests.

After metadata are collected, it should then be transformed into structures that you would expect to see in a sitemap. It could be transformed directly as <url> nodes and embedded in a sitemap as string, or as objects that will be consumed by sitemap generating libraries. No matter how you do it, this step is relatively generic.

src/routes/sitemap.xml/+server.ts
import type { RequestHandler } from '@sveltejs/kit';
import { loadPosts } from '$lib/content';
export const GET: RequestHandler = async ({ url }) => {
  // this function could load from local files with import.meta.glob() or fetching data from remote, depending on how you store your data
  const posts = loadPosts();

  return new Response(
    `
<?xml version="1.0" encoding="UTF-8" ?>
<urlset
  xmlns="https://www.sitemaps.org/schemas/sitemap/0.9"
  xmlns:xhtml="https://www.w3.org/1999/xhtml"
  xmlns:mobile="https://www.google.com/schemas/sitemap-mobile/1.0"
  xmlns:news="https://www.google.com/schemas/sitemap-news/0.9"
  xmlns:image="https://www.google.com/schemas/sitemap-image/1.1"
  xmlns:video="https://www.google.com/schemas/sitemap-video/1.1"
>
${posts
  .map(({ id, updatedAt }) => {
    return `<url>
              <loc>${url.origin}/blog/${id}</loc>
              <lastmod>${updatedAt}</lastmod>
              </url>`;
  })
  .join('')}
</urlset>`.trim(),
    {
      headers: {
        'Content-Type': 'application/xml',
      },
    },
  );
};

This approach is imperative, and the pitfall for it is you have to duplicate your routing configuration in code and maintain them, as metadata do not have information on what routes your SvelteKit project has. Say the id of your posts are all slugified strings, such as my-post-title, and in your SvelteKit project you have a route /blog/[slug]. You have to transform your data and prepend /blog when you build your sitemap. A few months later, your project manager decided to add i18n support in your website, and you changed the route to /[lang]/blog/[slug]. I bet you a pound to penny someone will forget to update the sitemap.

Creating sitemap for SvelteKit projects with super-sitemap

The cleanest and the easiest way to create a sitemap in SvelteKit right now is probably with the super-sitemap. Instead of using import.meta.glob function to import all the data files, super-sitemap only imports route definition files such as +page.svelte, +page.md and +page.svx and tracks all routes you have in your project. You have to exclude or provide metadata for all route parameters for every route, otherwise it will throw error.

Building sitemap with super-sitemap is more declarative, as it expects you to provide values for route parameters found in your routes only, and path building is managed by it. You do not need to duplicate routing configurations in code.

src/routes/sitemap.xml/+server.ts
import * as sitemap from 'super-sitemap';
import type { RequestHandler } from '@sveltejs/kit';
import { loadPages, loadPosts } from '$lib/content';
export const GET: RequestHandler = async ({ url }) => {
  // you can pull metadata from wherever you like. It could be from `import.meta.glob` or from a CMS.
  const posts = loadPosts({
    pinned: false,
    description: true,
  });

  return await sitemap.response({
    origin: url.origin,
    excludeRoutePatterns: [
      // you can exclude routes from the generated sitemap.
      '^/author.*',
      '^/tag.*',
      '^/archive.*',
    ],
    paramValues: {
      // and you provide your metadata to super-sitemap, using route with route parameter as key, so it can substitute those routes for you in the sitemap output.
      '/blog/[slug]': posts.map((x) => {
        return {
          values: [x.id],
          lastmod: x.updatedAt,
        };
      }),
    },
  });
};

With super-sitemap covering route tracking and sitemap generation, you can focus on retrieving metadata for your routes. This separation of concerns makes the project easier to refactor and maintain down the road.

Hugo Sum

A Hongkonger living in the UK. The only thing I know is there is so much I don't know.

Enjoy reading my blog?

Subscribe to a monthly newsletter and join my adventure on software development.

© 2025 Hugo Sum. All Rights Reserved.