Richard Beattie

SvelteKit Sitemap

SvelteKit came out in public beta a little over a month ago and I’ve finally gotten around to trying it out. I’ll write up my thoughts elsewhere but I’ve moved r-bt.com over to SvelteKit and replaced my Notion CMS with markdown. The reason being I want to be able to use custom components. Anyway, one problem I had was creating a sitemap.xml for my static build. SvelteKit dosn’t support creating sitemaps automatically although it might in the future.

Instead I made a post build step. Some notes about this:

  • I’m using Node v14 if you use an earlier version you might need to change import to require
  • I use @sveltejs/adapter-static to build a static site which is stored in /build

The Script

1. Install the dependencies

npm install -D fast-glob xmlbuilder2

2. Create a new file generate-sitemap.xml and add the following:

import fs from 'fs';
import fg from 'fast-glob';
import { create } from 'xmlbuilder2';
import pkg from './package.json';

const getUrl = (url) => {
	const trimmed = url.slice(6).replace('index.html', '');
	return `${pkg.url}/${trimmed}`;
};

async function createSitemap() {
	const sitemap = create({ version: '1.0' }).ele('urlset', {
xmlns: 'http://www.sitemaps.org/schemas/sitemap/0.9'
	});

	const pages = await fg(['build/**/*.html']);

	pages.forEach((page) => {
const url = sitemap.ele('url');
url.ele('loc').txt(getUrl(page));
url.ele('changefreq').txt('weekly');
	});

	const xml = sitemap.end({ prettyPrint: true });

	fs.writeFileSync('build/sitemap.xml', xml);
}

createSitemap();

3. Update your package.json

{
    url: "https://your-url.com",
    scripts: {
        ...,
        "postbuild": "node --experimental-json-modules ./generate-sitemap.js",
    }
}

The Explaination

To make the sitemap we’re going to build the site, glob all the .html files, and write the xml back to the /build directory.

Before starting install the dependecies

npm install -D fast-glob xmlbuilder2

Now create a new file generate-sitemap.xml

First, let’s get the files we need:

import fg from 'fast-glob';

async function createSitemap() {
	const pages = await fg(['build/**/*.html']);

	console.log({ pages });
}

If you run this you should get an array with the paths of all your pages

{
	pages: [
'build/index.html',
'build/blog/index.html',
'build/about/index.html',
'build/learning/index.html',
...
	];
}

Next we’ll use xmlbuilder to create the xml objects

import { create } from 'xmlbuilder2';

const sitemap = create({ version: '1.0' }).ele('urlset', {
	xmlns: 'http://www.sitemaps.org/schemas/sitemap/0.9'
});

and we just loop through the pages adding each as a url object with a loc and changefrequency to the sitemap

pages.forEach((page) => {
	const url = sitemap.ele('url');
	url.ele('loc').txt(page);
	url.ele('changefreq').txt('weekly');
});

Finally we turn the sitemap into a string and write it to a file using fs.writeFileSync

import fs from 'fs';
import fg from 'fast-glob';
import { create } from 'xmlbuilder2';

async function createSitemap() {
	const sitemap = create({ version: '1.0' }).ele('urlset', {
xmlns: 'http://www.sitemaps.org/schemas/sitemap/0.9'
	});

	const pages = await fg(['build/**/*.html']);

	console.log({ pages });

	pages.forEach((page) => {
const url = sitemap.ele('url');
url.ele('loc').txt(page);
url.ele('changefreq').txt('weekly');
	});

	const xml = sitemap.end({ prettyPrint: true });

	fs.writeFileSync('build/sitemap.xml', xml);
}

createSitemap();

Except we have a problem. If you run this code:

node generate-sitemap.js

and go to build/sitemap.xml you’ll see that the locs are something that looks like:

build/learning/why-is-it-so-hard-to-find-a-domain/index.html

while we want it to be:

https://r-bt.com/learning/why-is-it-so-hard-to-find-a-domain/

To fix this go to your package.json and add

{
	"url": "https://your-url.com"
}

Then in generate-sitemap.js we’ll import package.json and append the url to the pages paths. We’ll also remove the first 5 characters build/ and index.html

import pkg from './package.json';

const getUrl = (url) => {
	const trimmed = url.slice(6).replace('index.html', '');
	return `${pkg.url}/${trimmed}`;
};

Node.js dosn’t yet importing .json files so need to run this script with the --experimental-json-modules flag

node --experimental-json-modules ./generate-sitemap.js

and you’re sitemap should be generated and valid 🎉

To get it to run whenever you build the site go back to package.json and in scripts add

{
    scripts:{
        ...,
        "postbuild": "node --experimental-json-modules ./generate-sitemap.js",
    }
}