Last week I refactored parts of this website and accidentally broke the endpoint that creates this sitemap. I decided to read up on doing sitemaps the right way. Here is what I learned.
Web developers often assume that Google will only index their site regularly if they have a sitemap. But is this true? In the Google Search Console docs, Google answers the question “Do I need a sitemap?” with “it depends.” Google recommends a sitemap when
You do not need a sitemap if
The website you’re reading this on is small and all pages are discoverable by crawlers, so I wouldn’t strictly need a sitemap. Still, I submitted one in May 2021 as an initial SEO boost. You can see when Google last read a sitemap in the Search Console. For my site it was almost two years ago (at the time of writing):

Google supports different types of sitemaps. If your site already has an RSS feed, you can submit the feed URL as a sitemap and call it a day. The most common sitemap type is XML. A simple XML sitemap that indexes only the homepage looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://maier.tech/</loc>
<lastmod>2023-02-28</lastmod>
</url>
</urlset> Every indexed page goes in a <url> tag. The <loc> tag contains the URL of the indexed page. The <lastmod> tag contains the last modified date. You may have encountered blog posts that mention
two additional tags, <priority> and <changefreq>. There is no need to worry about choosing
values for these tags. Google ignores both. And so does Bing.
SvelteKit’s SEO docs show an example of a
sitemap implemented as an endpoint in src/routes/sitemap.xml/+server.ts. The GET handler
assembles an XML string to which you add the relevant routes. You don’t need to include every route,
only those you want indexed by Google (for example, your posts). The catch is figuring out how to
retrieve the entries for your sitemap. There’s no copy-paste blueprint because it depends on how you
manage your content. Below, I’ll walk through the high-level steps.
I write my posts in Markdown and use Content Collections to access post metadata. Retrieving all posts for a sitemap requires one server-side import:
import { all as posts } from '$lib/server/collections/posts'; The import comes from this file:
import { allPosts } from 'content-collections';
export const all = allPosts.toSorted((a, b) => {
return b.publishedDate.localeCompare(a.publishedDate);
}); and all the heavy lifting is done by Content Collections. Less fancy solutions are also totally fine. For example, you could add an endpoint that returns the posts from a manually maintained JSON file. If you manage your posts in a CMS, that endpoint would retrieve them via an API call.
Create an endpoint at src/routes/sitemap.xml/+server.ts and add a GET handler. My handler uses
the post collection:
import { ORIGIN } from '$env/static/private';
import { all as posts } from '$lib/server/collections/posts';
import type { RequestHandler } from './$types';
export const GET: RequestHandler = async () => {
// Create sitemap entries for posts.
const postEntries = posts.map(
(post) => `\t<url>
<loc>${ORIGIN}${post.path}</loc>
<lastmod>${post.lastmodDate ? post.lastmodDate : post.publishedDate}</lastmod>
</url>`
);
// Add additional collections to this array.
const pages = [...postEntries];
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${pages.join('\n')}
</urlset>`;
return new Response(sitemap, {
headers: {
'Cache-Control': 'max-age=0, s-maxage=3600',
'Content-Type': 'application/xml'
}
});
}; This endpoint reads all posts from the posts collection and wraps them into <url> tags in the
format discussed in the first part of this post. The content type of the response is application/xml. Since the paths in my post collection are relative, I need to prepend the origin
from environment variable ORIGIN because sitemaps require absolute URLs.
The above code is a simplified version of my actual endpoint,
which you can explore on GitHub. If you want to see an example of how to define content collections
with transformations, look at the file content-collections.ts.
If your SvelteKit site uses adapter-static, you can
use the package svelte-sitemap. With this package,
instead of implementing an endpoint for a sitemap, you can configure the postbuild hook in package.json to scan all routes in the build directory and create build/sitemap.xml. This
approach only works with adapter-static, since svelte-sitemap cannot determine all possible routes
for other adapters.