Learn SvelteKit - Part 3: Unidirectional Data Loading

Matías Hernández
author
Matías Hernández
Learn SvelteKit - Part 3: Unidirectional Data Loading

Learn SvelteKit

You are currently reading an introduction to SvelteKit blog series. This series takes you from the start of SvelteKit through the essential API's to get you up and running using SvelteKit for your projects.


In the previous articles, we covered the first cornerstone of building an application with SvelteKit, you now know how to setup a new project and how add routes, even dynamic routes, but that alone doesn’t create an application, there is one more thing to do.

The next concept or feature that we need to review is the process of data loading, or how SvelteKit injects data into the pages.

Remember, each route folder can have a +page.svelte file that defines the page to be rendered, but at the same time it can have a +page.js file. This file is in charge of defining the required logic to retrieve some data and to export a well-known function named load.

Whichever is returned by this function inside the +page.js file will be available as data into the sibling +page.svelte file:

/** @type {import('./$types').PageLoad} */
export function load({ params }) {
return {
title: `This title comes from some other logic`,
content: `This was server rendered`
};
}

The example code above shows the structure of the load function inside the +page.js file.

It’s just a function that returns an object.

The return value of this function will be available in the page code as data.

Is worth noting that the name of this function is reserved by SvelteKit and that this file can’t export other functions (there are a few more reserved ones that can be exported that we will cover in future chapters)

<script>
/** @type {import('./$types').PageData} */
export let data;
</script>
<h1>{data.title}</h1>
<div>{@html data.content}</div>

The types used in this examples (written as JSDoc) are generated by sveltekit, in this way you get full type safety across the wire.

Is worth noticing that the load function here will run both on the server and in the browser, this means that this load function can hold any server side-only code as database direct access.

Ready to pick up the pace?

Enter your email and receive regular updates on our latest articles and courses

What do you want to take to the next level?

What if I need to access a database?

Well, sometimes you need to only execute code on the server, for that you can use another + prefixed file. the +page.server.js (or .ts if you prefer.)

This reserved named file works exactly as +page.js but will run code only on the server:

// src/routes/topic/[topic]/+page.server.js
import { error } from '@sveltejs/kit'
import db from '$lib/database'
/** @type {import('./$types').PageServerLoad} */
export async function load({ params }) {
try {
const {title, content} = await db.getPost(params.slug)
return {
post: {
title,
content,
}
};
}catch(err){
throw error(500, err)
}
}

You can see in the example above that now, you can use server side reserved code, with a very similar structure, the only addition here is that now, the function needs to be async and because of that it can fail.

To guard ourselves against the possible failure the code is wrapped within a try/catch block. If something goes wrong an error is thrown using the error function.

There is a default error page on SvelteKit but you can customize that to your liking by adding an +error.svelte file to some of the routes. This component will act in a similar way as the layout one, affecting all the nested routes underneath.

To load this data into the the sibling +page.svelte you’ll do same as before:

<script>
/** @type {import('./$types').PageData} */
export let data;
</script>
<h1>{data.post.title}</h1>
<div>{@html data.post.content}</div>

Let’s go back to our application, a this point the current file structure should looks like the following:

src
└──routes
├── +layout.svelte
├── +page.svelte
└── todo
├── [id]
│ ├── +page.js
└── └──+page.svelte

Layouts can also load data

We already review the concept of layouts, an svelte component that can wrap pages, with shared structure, like a navigation bar, but what happens if you need to load data that can be used in many pages?.

You can use the layout to pass data to the child pages.

The +layout.svelte behaves in a similar way as the +page.svelte it can load data through the sibling reserved file +layout.js (o . ts) that will export a load function that will run on client and server, or using server-only code through +layout.server.js.

The data returned through the layout load function will be available to every child route (remember, layouts will affect every nested route).

Let’s add a new +layout.server.js file at the root level:

/** @type {import('./$types').LayoutServerLoad} */
export async function load({ page }) {
const res = await fetch(`https://api.example.com/data`);
const articles = await res.json();
return {
articles,
};
}

The load function fetches data from an API and returns it as an object. The returned data will be available to every child route that uses the layout.

Now, let’s update the root layout component on +layout.svelte :

<script>
// Global CSS
import "../app.css"
/** @type {import('./$types').LayoutData} */
export let data;
</script>
<nav>
<a href="/">Home</a>
<a href="/blog">Blog</a>
</nav>
<main>
<ul>
{#each data.articles as item}
<li>{item.title}</li>
{/each}
</ul>
<slot />
</main>
<footer>
This is the footer
</footer>

Now the +layout.svelte component receives the data from the load function and would render the data onto the screen.

Until this point, we reviewed how SvelteKit handle unidrectional data flow, there is a more advanced way to access your data by using the $page store. We will review this process in the next article of the series!

Ready to pick up the pace?

Enter your email and receive regular updates on our latest articles and courses

What do you want to take to the next level?