Skip to content

SvelteKit (v2) - Prerender Dynamic Paths for Static Site Generation

Posted on Jan 3, 2024 in Blog Posts


Version Reference

At the time of writing:

  • svelte's version is 4.2.8
  • @sveltejs/kit's version is 2.0.6
  • @sveltejs/adapter-static's version is 3.0.1.

Overview

In this post, we'll explore real-world examples for using entries to prerender dynamic paths when using the static adapter for static site generation (SSG) in SvelteKit . This site is deployed to AWS S3 as a static site, and so I've learned a few tricks along the way.

SvelteKit using the static adapter will automatically generate any non-dynamic paths and dynamic paths it finds via crawling links on the site from the root route. This covers a lot of use cases, especially if your site links to all of the dynamic pages.

If your site doesn't link to all of the dynamic pages though, SvelteKit doesn't know those pages exist or that it needs to prerender them.

Prerender Methods

There are configurations to direct SvelteKit to prerender for dynamic path variables, and it offers two methods to do so.

Kit Config Prerender Entries

In the svelte.config.js file, SvelteKit can be directed to a list of pages to prerender or start crawling from to prerender.1 This by default is set to ["*"], which means SvelteKit will prerender any routes that don't require parameters. It doesn't prerender routes with parameters because it doesn't know what those parameters should be. Any optional parameters are evaluated as being undefined.

If there are only a few dynamic path pages to prerender, or there are pages that link to all the dynamic path pages to prerender, listing them all in the config can be a good option.

svelte.config.js
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter({
pages: 'build',
assets: 'build',
fallback: 'index.html',
precompress: false,
strict: true,
}),
prerender: {
entries: [
"*",
"/post/hello-world",
"/post/meow"
],
},
}
};

If there are a lot of dynamic paths to prerender, the next option may be a better fit.

Page Options Entries Generator Function

Alternatively, a list of pages to prerender can be provided via the page options entries export, which should be an entry generator function. 2

Entry generator functions can be synchronous or asynchronous and should return an array of entry objects, or return a promise of array of entry objects.

Each entry object should contain the parameters to generate the page.

For route /post/[slug]/[lng], the entry object would look like:

// /post/[slug]/[lng] entry object
const entryObject = {
slug: "hello-world",
lng: "en"
}

The entry generator function can be anything that returns an array of entry objects, or a promise of the same.

For example, if there was a route /post/[slug], the returned objects in the array would look like {slug: "hello-world"} to cover the dynamic path variable.

src/routes/post/[slug]/+page.server.ts
import type {EntryGenerator} from "./$types";
export const entries: EntryGenerator = () => {
return [
{slug: "hello-world"},
{slug: "meow"}
]
}
Tip

Typescript types are available to +page.ts, +page.server.ts, or +server.ts by importing EntryGenerator from ./$types.

As another example, let's look at a route /[lng]/post/[slug]. SvelteKit needs to know about both the lng and the slug for this one to cover all the content.

src/routes/[lng]/post/[slug]/+page.server.ts
import type {EntryGenerator} from "./$types";
export const entries: EntryGenerator = () => {
return [
{
lng: "en",
slug: "hello-world"
},
{
lng: "es",
slug: "hello-world"
}
]
}

Entry Generation Strategies

Arrays or Objects

On this site, there is a category map with slugs, names, etc. It looks like this:

src/lib/config.ts
export const postCategorySlugsArray = ["chakra-ui", "react", "svelte", "sveltekit"] as const
type LanguageCode = string;
type PostCategorySlug = (typeof postCategorySlugsArray)[number]
type PostCategoryConfig = Record<"slug", PostCategorySlug> & Record<"name" | "description", string> & Record<"languages", LanguageCode[]>
type PostCategory = Record<PostCategorySlug, PostCategoryConfig>
export const postCategories: Record<PostCategorySlug, PostCategoryConfig> = {
sveltekit: {
name: "SvelteKit",
description: "SvelteKit is a framework for developing web applications with Svelte.",
slug: "sveltekit",
languages: ["en", "es"]
}
}

If we have a route like /posts/[category], we could use the postCategorySlugsArray directly.

src/routes/posts/[category]/+page.server.ts
import type {EntryGenerator} from "./$types";
import {postCategorySlugsArray} from "$lib/config";
export const entries: EntryGenerator = () => {
// Map our postCategories to an array
return postCategorySlugsArray.map((category) => {category})
}

If we have a route like [lng]/posts/[category], we can use an entry generator function to map the array to prerender entries.

src/routes/[lng]/posts/[category]/+page.server.ts
import type {EntryGenerator} from "./$types";
import {postCategories} from "$lib/config";
export const entries: EntryGenerator = () => {
// Map our postCategories to an array
return Object.entries(postCategories).map(([category, categoryConfig]) => {
// For each language, add an entry object to the returned array
return categoryConfig.languages.map((languageCode) => ({lng: languageCode, category: categoryConfig.slug}))
})
}

File Paths

If the site is something like a blog using markdown files for content, you may need to generate a path for each file.

Suppose there is a route /post/[slug] to render individual posts, and each slug corresponds to a markdown file of the same name in the posts directory.

We can create an entries generator that reads each file name and formats it so it doesn't include the file extension.

src/routes/post/[slug]/+page.server.ts
import fs from 'fs/promises';
import path from 'path';
import type {EntryGenerator} from "./$types";
export const entries: EntryGenerator = async () => {
// Resolve the root post directory
const postsPath = path.resolve('post');
// Read the files in the directory
const files = await fs.readdir(postsPath);
// Map the files array into the EntryGenerator format and return it
return files.map((file) => {
// Split the file name by .
const parseFileName = file.split(".");
// Join all file name parts except for the last one (file extension)
const fileName = parseFileName.slice(0, parseFileName.length - 1).join("");
// Return the file name as the slug name
return {slug: fileName}
});
}

Footnotes


1. SvelteKit Documentation - Prerender configuration and the entries option.
2. SvelteKit Documentation - Page options entries generator function.