DEV Community

loading...

SvelteKit Sitemap

Richard Beattie
Gap Year Student | MIT '25
Originally published at r-bt.com Updated on ・3 min read

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
Enter fullscreen mode Exit fullscreen mode

2. Create a new file generate-sitemap.xml in the root of your project (e.g. beside svelte.config.cjs, etc) 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();
Enter fullscreen mode Exit fullscreen mode

3. Update your package.json

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

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
Enter fullscreen mode Exit fullscreen mode

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 });
}
Enter fullscreen mode Exit fullscreen mode

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',
        ...
    ];
}
Enter fullscreen mode Exit fullscreen mode

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'
});
Enter fullscreen mode Exit fullscreen mode

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');
});
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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

node generate-sitemap.js
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

while we want it to be:

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

To fix this go to your package.json and add

{
    "url": "https://your-url.com"
}
Enter fullscreen mode Exit fullscreen mode

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}`;
};
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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",
    }
}

Enter fullscreen mode Exit fullscreen mode

Discussion (2)

Collapse
teds31 profile image
Teddy Stanowski

Can you elaborate on where the file should be? in root? in src?

Collapse
rbt profile image
Richard Beattie Author

It can be anywhere but best would be in the root of your project
e.g.

my-sveltekit-app:
├── svelte.config.cjs
├── generateSitemap.js
├── src
│   ├── ...
├── ...
Enter fullscreen mode Exit fullscreen mode

If you want to put it somewhere else you'll need to reflect that in the prebuild command. I've updated the post to make this clearer, thanks for the feedback!