Prerender all SvelteKit pages with Prismic as the CMS

Reece May • April 4, 2021 • 4 min read

prismic tips headless

Originally trying SvelteJS Sapper to build a site with Prismic was a bit tricky, mainly trying to get the systems to work together and also finding out that the hot module reloading was bust...

When testing SvelteKit out (I had read about it on Sapper docs somewhere 🤷‍♂️) but then there was the beta release so I tried it out and also read the Prismic blog post on setting it up and it worked a lot better, the experience was similar to Next and the result was a really really fast build. (Like stupid quick compared to other things)

Yet there was a problem, dynamic static routes weren't built without the svelte:prefetch...

Building a route list before pre-rendering.

I tested different ways to get the site to fetch data and basically 'hydrate' stuff at build time. Because interestingly even with the static-adapter it didn't render pages that were dynamic [uid].svelte. P.S. use npm i -D @sveltejs/adapter-static@next is the working one.

So you can imagine the confusion when Netlify a) doesn't build the site properly and b) the static site has 404 pages where there should be pages.

The solution I decided to use, as it has the best support at present is to use a Node script to fetch the list of pages that you want, map them through a linkResolver function to build the appropriate routes for Svelte to render and make it available before Svelte builds.

Building the dynamic routes 🗺

The first file is called utils/routelist.js, this is called before running Svelte's build process and generates your routes. It is only needed at build time. So when you put it on the server for production.

import Prismic from '@prismicio/client';
import path from 'path';
import fs from 'fs';
import { cwd } from 'process';

const repository = process.env.PRISMIC_REPOSITORY 
    ? process.env.PRISMIC_REPOSITORY 
    : 'CHANGE_THIS_PLEASE';

const apiEndpoint = `https://${repository}.cdn.prismic.io/api/v2`;
const Client = Prismic.client(apiEndpoint);

// Our link resolver that will be used to map the routes 🗺
const linkResolver = (doc) => {
    if (doc.type === 'blog') {
        return `/blog/${doc.uid}`
    }

    if (doc.type === 'categories') {
        return `/category/${doc.uid}`
    }

    return `/${uid}`;
}

/**
 * This is our build function. It contains the query.
 * It uses the link resolver to build the routes.
 * It then writes it to disk in the same dir.
 */
const build = async () => {
    let query = await Client.query([
        Prismic.Predicates.at('document.type', 'blog'),
        Prismic.Predicates.at('document.type', 'categories'),
        Prismic.Predicates.at('document.type', 'homepage'),
    ]);

    /**
     * Svelte uses the `*` to render the other pages that aren't dynamic
     */
    let pages = ['*']

    /**
     * Convert the docs into the related link for Svelte to render.
     */
    let result = query.results.map(linkResolver);

    pages.push(...result)

    /**
     * We create a JSON file that is used to build the site.
     */
    fs.writeFileSync(
        path.join(cwd(), './utils/routelist.json'),
        JSON.stringify(pages)
    )

    console.log('written: ', pages);
}

build();

A quick summary of what that all does is basically uses the API to get the docs we want and their UID, map them, then write them to disk as an array in a JSON file.

This has two benefits, we don't have to commit the JSON to git and we don't have to parse the JSON file when importing it.

Updating the Svelte config file

To make use of the built pages, you can edit your svelte.config.js file like so:

+ const static = require('@sveltejs/adapter-static');
- const prismic = require('./adapter-prismic');
const pkg = require('./package.json');
+ const pages = require('./utils/routelist.json');

/** @type {import('@sveltejs/kit').Config} */
module.exports = {
    kit: {
        // By default, `npm run build` will create a standard Node app.
        // You can create optimized builds for different platforms by
        // specifying a different adapter
+       adapter: static(),

        // hydrate the <div id="svelte"> element in src/app.html
        target: '#svelte',

        prerender: {
+           crawl: true,
+           enabled: true,
+           force: true,
+           pages: [...pages],
        },

        vite: {
            ssr: {
                noExternal: Object.keys(pkg.dependencies || {})
            }
        }
    }
};

This will import the json file that is basically an array of strings, this is then destructured into the pages key for the prerender.

Updating package.json

To make sure the files are triggered correctly, you can add the following to the package.json scripts key:

    "scripts": {
        "dev": "svelte-kit dev",
        "build": "npm run routes && svelte-kit build", /* Updated */
        "routes": "node ./utils/routelist", /* NEW */
        "start": "svelte-kit start"
    },

This will make it possible that now your dynamic pages in Svelte are prerendered when exporting to a static version using Prismic or another headless CMS I guess.

Cleaning it up

I added the exported file to my gitignore, as it really isn't needed as it is once off during the build process. So for this specific example add the following to the root .gitignore for your project: /utils/routelist.json

That should be about all to make a clean, fast, and non-404 static site with SvelteKit

0_o