Aral Balkan

Mastodon icon RSS feed icon

Passing data from layouts to pages in SvelteKit

Sequence diagram representing the data flow detailed in the text of this post.

Data flow from layout to page (slot) in SvelteKit

I’m prototyping Basil, the free and open hosting client that’s going to power small-web.org, in SvelteKit and one thing I want to ensure from the outset is that the app is not hardcoded for our use so that anyone can easily set up a Small Web host simply by installing and configuring it.

Now, ideally, I want that configuration data to be loaded and rendered on the server (SSR/SSG) once.

Given that you cannot use Node APIs in layouts/pages even when they’re rendered on the server, the way to do this is to use an endpoint and consume it on the server in your main layout and then pass the data to any pages the layout has slotted into it using the context.

To cut a long story short, I struggled a bit to get this running initially as there are two separate “context” concepts in SvelteKit1 and I couldn’t find any code samples when I was starting out with it. So here’s the code sample I wish I had.

Remember that SvelteKit is in beta and this might all change. There’s also the chance that, while this works, I’m still “holding it wrong” and there might be an even more straightforward way to achieve this that I do not know about.

The endpoint

The rest of this post assumes that you’ll be working with the following endpoint, defined in src/routes/config.js:

import os from 'os'
import fs from 'fs'
import path from 'path'

// Load the configuration from ~/.small-tech.org/basil/config.json
const config = JSON.parse(fs.readFileSync(path.join(os.homedir(), '.small-tech.org', 'basil', 'config.json'), 'utf-8'))

// Return the configuration in response to a GET request on /config
export async function get() {
  return {
    body: config
  }
}

Layout: load the data

You load the data in the load() handler.

For example, your layout in src/routes/$layout.svelte might resemble the following:

<script context='module'>
	import '../app.css'
	import Nav from '../lib/Nav.svelte'

	export async function load({ page, fetch, session, context }) {
		const url = '/config'
		const res = await fetch(url)

		if (res.ok) {
			const config = await res.json()
			return { context: { config } }
		}

		return {
			status: res.status,
			error: new Error(`Could not load ${url}`)
		};
	}
</script>

<Nav/>
<slot/>

Two key things to note here:

  1. This is a context=‘module’ script, not a regular script tag.
  2. We’re returning an object with a context property from the load() handler.

So what’s this context property?

According to the docs, it is exactly what we need:

This will be merged with any existing context and passed to the load functions of subsequent layout and page components.

This only applies to layout components, not page components.

The important thing to note is that it is not the same as the context you manipulate using the setContext() and getContext() methods. In fact, if you try to access those methods from context='module' scripts, you will get an error.

Page: load the data from the context

Now, let’s say you have a page at src/routes/index.svelte that gets slotted into your layout.

To use the configuration data, you just need to get it from the context you returned in your layout:

<script context='module'>
	let config
	export async function load({ page, fetch, session, context }) {
		config = context.config
		return true
	}
</script>

<main>
	<h1>{config.name}</h1>
	<h2>About this Small Web host</h2>
	<p>{config.description}</p>
</main>

<style>
	/* etc. */
</style>

That’s it!

Hope it helps in case you were stuck trying to do the same thing :)

Like this? Fund us!

Small Technology Foundation is a tiny, independent not-for-profit.

We exist in part thanks to patronage by people like you. If you share our vision and want to support our work, please become a patron or donate to us today and help us continue to exist.


  1. You can actually also use the other type of context in Svelte/SvelteKit for this also but it is a far more convoluted process that involves returning a prop from the load() handler in your layout and storing it in the context using setContext() from a separate script tag (not your context='module' script tag) in the layout, and, finally, using getContext() to retrieve it from the script tag (not your context='module' script tag) of your page. Whew! ↩︎