Phil Kruft

How to Build a Static SvelteKit Site

Published: February 1, 2023

In this article I will show you the fundamental steps to use SvelteKit as a Static Site Generator and elaborate on some related topics afterwards.

The case for static sites

Getting into SvelteKit with little to no experience in Node.js is probably easiest by building a fully static site and removing the need for a Node server completely. A static site will run fairly well on the cheapest of hosting solutions and especially a SvelteKit static site will obliterate any common CMS in terms of performance when run on the same server.

Other benefits of a static site are security, easy version control, portability and arguably also flexibility, compared to most CMS.

And a SveteKit static site is not just like any collection of html files you link together, like we used to do back in the days. SvelteKit static sites come with client side navigation and data preloading to make any site truly fast. This blog is a SvelteKit static site, btw.

Creating a new SvelteKit site

Terminal
npm create svelte@latest my-static-site

Pick ‘Skeleton project’ to get a clean install. The installer will ask you a few questions. I’ll pick JavaScript as I am not yet comfortable with TypeScript. You can decide if you want ESLint, Prettier and so on. None of the options are needed for our small static site.

Terminal
cd my-static-site
npm install
npm run dev -- --open

Your SvelteKit site should open in the browser and while you keep the terminal open it will nearly instantly reflect all changes you make to the code.

SvelteKit routing basics

We’ll have to add a few subpages, what would be the point otherwise?

SvelteKit uses a filesystem-based router, which means that the folders and files within your /src/routes folder directly represent the routes of your project. We’re not going to go in too much detail on the subject as routing is one of the core concepts of SvelteKit, which means you should really study the docs on it.

Just to have something to work with, we are going to create a few routes, add some content and link them all up in a layout file.

Your pages are files which have to be called +page.svelte. So creating something like this:

📂 src
┣ 📂 routes
┃ ┣ 📂 one
┃ ┃ ┗ 📄 +page.svelte
┃ ┣ 📂 three
┃ ┃ ┗ 📄 +page.svelte
┃ ┣ 📂 two
┃ ┃ ┗ 📄 +page.svelte
┃ ┗ 📄 +page.svelte
┗ 📄 app.html

will give you these routes:

/
/one
/two
/three

We’ll just add a simple navigation to all pages via a layout file, so we actually can test out the client side navigation.

/+layout.svelte
1<nav>
2 <a href="/">Home</a>
3 <a href="/one">One</a>
4 <a href="/two">Two</a>
5 <a href="/three">Three</a>
6</nav>
7 
8<slot />

We’ll just need to add anything to the pages so we actually see something happening when we navigate:

/src/routes/one/+page.svelte
1<h1>One</h1>
2 
3<p>Some example content</p>

All the settings needed to make a site fully static

For a completely static site we use the static adapter. So we install it as a dev dependency like so:

Terminal
npm install -D @sveltejs/adapter-static

Then we replace adapter-auto by the newly added adapter-static.

/svelte.config.js
-import adapter from '@sveltejs/adapter-auto';
+import adapter from '@sveltejs/adapter-static';
3 
4/** @type {import('@sveltejs/kit').Config} */
5const config = {
6 kit: {
7 adapter: adapter()
8 }
9};
10 
11export default config;

And all that’s left now is to tell SvelteKit that all pages should be prerendered. The most convenient way to do this is in a root layout file.

/src/routes/+layout.js
1export const prerender = true;

There’s one more setting you might need to change depending on your hosting. By default SvelteKit builds routes/about to about.html. That means, when receiving a request to your-domain.tld/about, your server needs to serve the about.html file.

If it is not set up to do that it might be changed, for example on an Apache server by means of an .htaccess file with rewrite rules. Or you can add the following setting:

/src/routes/+layout.js
1export const prerender = true;
+export const trailingSlash = 'always';

The trailing slash setting on ‘always’ does change how your static site is built. routes/about will no longer lead to a about.html but instead to an index.html in an /about folder.

So if in doubt just add this setting. It will make your build folder slightly more complex but it will work anywhere out of the box.

Build and preview your static site

Now just run npm run build and Vite will build your site inside the build folder. The ‘official’ way to test out that site is to run npm run preview.

I prefer npx serve build as it sometimes gets better results (meaning closer to how it will really behave on the server).

That’s already it. You can just grab the content of the build folder and upload it to any cheap shared hosting or whatever you have available.

Now you already know the basics to get a simple static site built with SvelteKit. Read on if you want to learn a few more related concepts. I plan to add more in the future and would love to hear your ideas on what could be useful.

Adding sleek page transitions

Adding some nice page transitions, for the whole page or only the content area, can be an easy upgrade for your site. Check out my blog post on smooth page transitions if you want to look into that.

Adding meta data

We will store information that’s supposed to end up in the <head> tag like the page title or meta description in a +page.js file that is stored alongside the +page.svelte file.

/src/routes/one/+page.js
1export async function load() {
2 return {
3 title: "Page One"
4 }
5}

And this is how we retrieve and use the meta data in our root layout:

/src/routes/+layout.svelte
1<script>
2 import { page } from '$app/stores';
3
4 export let data;
5</script>
6 
7<svelte:head>
8 <title>{$page.data.title ?? "My Fallback Title"}</title>
9</svelte:head>
10 
11<nav>
12 <a href="/">Home</a>
13 <a href="/one">One</a>
14 <a href="/two">Two</a>
15 <a href="/three">Three</a>
16</nav>
17 
18<slot />

I’m not going to go in detail on how all this works but if you are curious you can look up these concepts in the docs: Page data and $app/stores.

Keeping routes private

I have yet to find another way to protect routes from being included in a build that is as straightforward as to just move them out of the routes folder before building the project. If you know a simple way, let me know.

Be aware that even if you set export const prerender = false for certain routes or route groups and don’t link to them, their content will still be included in the build. It won’t be possible to load those URLs directly but on-page navigation would still load the content.

What you should know about preloading

This is what comes out of the box with any SvelteKit app:

/src/app.html
1<!DOCTYPE html>
2<html lang="en">
3 <head>
4 <meta charset="utf-8" />
5 <meta name="viewport" content="width=device-width" />
6 %sveltekit.head%
7 </head>
8 <body data-sveltekit-preload-data="hover">
9 <div style="display: contents">%sveltekit.body%</div>
10 </body>
11</html>

This attribute on the body tag sets the default behavior for all the on-page links of your site and this particular setting means that users only need to hover over an internal link for all the data of the linked page to get loaded.

That’s what we want to happen most of the time but there might be cases, like with very data-heavy pages on high-traffic sites where we don’t want to transfer a ton of data that might not even be requested in the end.

Check out the official docs on link options to see all possible options.

Hi y'all, consider this a disclaimer: I use this blog to write about subjects that are fairly new to me. I am in no way an expert in these subjects. - By writing these articles I hope to solidify my knowledge and potentially help out others. If you find errors or you'd like to help improve an article, just write to me.

Syntax highlighting powered by Torchlight