Svelte journey | SvelteKit: Introduction, Routing, Data Loading, Shared Modules
Tags: javascript, tutorial, svelte, frontend
Published: 2024-01-05 07:30:38

Welcome,

This article marks the beginning of our exploration of `@sveltejs/kit`! SvelteKit is a metaframework that contains the core toolset needed to build a variety of applications. In this article, we embark on a journey through key aspects: Routing, Data Loading, and Shared Modules, unveiling the power and simplicity that SvelteKit brings to web development.

## General Information

SvelteKit, a robust framework, offers a seamless transition from Server-Side Rendering (SSR) to a Single Page Application (SPA) after the initial load. Default configuration files, such as `svelte.config.js` and `vite.config.js`, along with a well-defined folder structure in `/src`, provide a solid foundation for building modern web applications. The `/routes` directory, following filesystem-based routing, makes navigation intuitive and efficient.

- SSR by default → improved load performance, SEO-friendly:
    - Transitions to SPA after the initial load;
    - Still, this can be [configured](https://kit.svelte.dev/docs/page-options) if needed.
- Default configuration files in the root folder:
    - [svelte.config.js](https://kit.svelte.dev/docs/configuration);
    - [vite.config.js](https://vitejs.dev/guide/features.html);
    - `package.json`, `.prettierrc`, etc.
- Default folder structure:
    - `/src`
        - `/app.html` — page template file;
        - `/app.d.ts` — [app-wide interfaces](https://kit.svelte.dev/docs/types#app) (for TypeScript);
        - `/routes` — the routes of the app;
        - `/lib` — files that can be imported via `$lib` alias in this folder;
    - `/static` — for any assets used in the app.

## Routing

SvelteKit's routing system is structured, with each `+page.svelte` file inside `src/routes` creating a corresponding page in the app. This approach simplifies route structuring and allows for easy parameterization using square brackets in the file path.

SvelteKit uses filesystem-based routing — the route path is the same as the folder path.

### Route structuring | `+page.svelte`

Every `+page.svelte` file inside `src/routes` creates a page in the app.

- e.g. `src/routes/+page.svelte` → `/`, `src/routes/about/+page.svelte` → `/about`

### Common layout | `+layout.svelte`

To have a common layout for some of the routes, you need to create a `+layout.svelte` file in the topmost folder that shares this layout. You need to specify the common layout here and put the `<slot />` element to indicate where pages should mount.

```jsx
src/routes/
├ about/
│ └ +page.svelte
├ +layout.svelte
└ +page.svelte

```

```jsx
// src/routes/+layout.svelte
<nav>
	<a href="/">home</a>
	<a href="/about">about</a>
</nav>

<slot /> // mount node for / and /about pages

```

### Route parameters

The file path should contain a folder with a name in square brackets — its name is the parameter’s name.

```jsx
src/routes/blog/[slug]/+page.svelte // single dynamic parameter
src/routes/blog/[bar]x[baz]/+page.svelte // multiple dynamic parameters (allowed with some static literal as a separator)

```

Example:

```jsx
// src/routes/+page.svelte
<a href="/param1xparam2">Navigate</a>

// src/routes/[bar]x[baz]/+page.svelte
<script>
	import { page } from '$app/stores';
	let bar = $page.params.bar;
	let baz = $page.params.baz;
</script>
bar: {bar} | baz: {baz} // bar: param1 | baz: param2

```

### Optional parameters

For example, you can have the locale as `/fr/...`, but you can also have a default locale that is not represented in the path. In that case, the parameter should be in double square brackets:

```jsx
// src/routes/[[lang]]/+page.server.js
export function load({ params }) {
	return { lang: params.lang ?? 'en' };
}

```

### Rest (any number of unknown) parameters

To match an unknown number of path segments, use a `[...rest]` parameter. This is useful to catch unhandled routes, similar to the `*` route definition in other routers.

Rest parameters do not need to go at the end — a route like `/items/[...path]/edit` or `/items/[...path].json` is totally valid.

### Param matchers — validate route parameter

Param matchers allow you to validate parameters. For example, if the user ID should be numeric only, you can do the following:

1. Create `src/params/<checker-name>.js`.
2. Define the checker for a route param `src/routes/<route>/[param=<checker-name>]`.

```jsx
// src/params/id-matcher.js
export const match = (value) => /^[0-9]+$/.test(value);
// src/routes/user/[id=id-matcher]

```

- In case of a failed match, a `404` error is returned.
- Matchers run both on the server and the client side.

### Route groups — subset of routes without changing a route’s path

Layouts are a way to share UI and data loading logic between different routes. If we need to use a layout without sharing data loading logic and without keeping the folder-to-path structure, we use [route groups](https://learn.svelte.dev/tutorial/route-groups).

For example, our `/app` and `/account` routes are behind authentication, while `/about` should be available without that one. This allows us to omit routes like `/authed/app` and keep just `/app`.

- Create a folder (group) with a name inside brackets, e.g., `src/routes/(authed)/+layout.server.js`

Unlike normal directories, route groups do not affect the URL pathname of the routes inside.

### Breaking out of layouts — independent children layout

Layouts are inherited by default. If you need to break this and have an independent layout:

- To break out, you need to add `@` to the page naming:
    - `page@.svelte` will reset all the hierarchy (except the `root` layout).
    - `page@<route-path-folder-name>.svelte` will reset to the `<route-path-folder-name>` layout.
    - `+layout.svelte` in the root **cannot be broken out.**

## [Data loading](https://kit.svelte.dev/docs/load)

Efficient data loading is a cornerstone of **SvelteKit**. The `load` function, available in various files like `+page.js`, `+layout.svelte`, and their server-side counterparts, empowers developers to fetch data dynamically. This data can then be seamlessly consumed in the corresponding Svelte files, providing a smooth integration of dynamic content.

- The `load({ url, route, params })` [function](https://kit.svelte.dev/docs/load#using-url-data) in `+page.js`, `+page.server.js`, `+layout.server.js`, `+layout.js` allows you to fetch data.
- It should then be consumed in the related Svelte file via `<script> export let data; </script>`.
- `$page.data` is an alternative way to access the page's data or if you need to access the page's data in the layout or in children. The `+page.svelte` component, and each `+layout.svelte` component above it, has access to its own data plus all the data from its parents:
    
    ```jsx
    <script>
    	import { page } from '$app/stores';
    </script>
    
    <svelte:head>
    	<title>{$page.data.title}</title>
    </svelte:head>
    
    ```
    
- Files without the suffix `.server.` run on both the client and server (unless you put `export const ssr = false` inside), while files with this suffix run on the server only (you can interact with DBs, use secrets here, etc.). More details on that: [universal vs server](https://kit.svelte.dev/docs/load#universal-vs-server). It is also covered further in the **Universal load functions** section.

### Page data | `+page.server.js`

- The `+page.server.js` file should be placed next to the target `+page.svelte`. This file runs only on the server, including client-side navigations.
- The `+page.server.js` file itself can import other non-Svelte files to fetch data.
- If you need to handle errors (e.g., incorrect route path), you can throw an `error` from `@sveltejs/kit`.

```jsx
// src/routes/blog/mocked-data.js (more realistic: import * as posts from '$lib/server/posts.service.js';
export const posts = [
	{
		slug: 'welcome',
		title: 'Welcome!',
		content:'<p>Nice that you are here</p>'
	}
];

// src/routes/blog/[slug]/+page.server.js
import { posts } from '../mocked-data.js'; // get data with some service
import { error } from '@sveltejs/kit';

export function load({ params }) { // function name must be load
	const post = posts.find((post) => post.slug === params.slug);

	if (!post) throw error(404, 'no post'); // throw an error if not found

	return { post };
}

// src/routes/blog/[slug]/+page.svelte
<script>
	export let data; // access point to exported values from +page.server.js
</script>

<h1>blog post</h1>
<h1>{data.post.title}</h1>
<div>{@html data.post.content}</div>

```

### Layout data | `+layout.server.js`

We can load data not only for some page but also for a child route under the same layout. The mechanism is the same as for page data.

From navigation to navigation, it is usually needed to refresh the data. [Invalidation](https://kit.svelte.dev/docs/load#rerunning-load-functions) should help there.

### Universal load functions — when data does not come directly from your server

Load from `+page.server.js` and `+layout.server.js` is a good use-case when getting data directly from a database or reading cookies, dealing with secrets. When you deal with some data loading during client-side navigation, you may simply not need to hit your server directly but rather some other source. For example:

- You need to access some external, 3rd party API.
- Use in-memory data if some data persists here, and only go to the server if no in-memory data is available.
- You need to return something non-serializable from the `load` function, such as a referenced JS object, component, store, etc.

To have such behavior, your `load` functions should not be in `+page.js` or `+layout.js` (no `.server.` suffix). With that:

- Functions will run on the server during server-side rendering.
- They will also run in the browser when the app hydrates or the user performs a client-side navigation.

### Server `load` and universal `load` together = server → universal → page data

When using both server `load` and universal `load`, the server `load` return value is not passed directly to the page, but to the universal `load` function as the `data` property.

### Access parent’s `load` result inside own `load` function

`+page.svelte` and `+layout.svelte` components have access to everything returned from their parent `load` functions. But how to access the parent's data right in the `load` function? We just need to use the `parent` prop as follows:

```jsx
// src/routes/+layout.server.js
export const load = () => ({ a: 1 });

// src/routes/sum/+layout.js
export async function load({ parent }) {
	const { a } = await parent();
	return { b: a + 1 };
}
// src/routes/sum/+page.js
export async function load({ parent }) {
	const { c } = await fetchCFromThirdParty();
	const { a, b } = await parent();
	return { d: a + b + c };
}

```

- The universal `load` function can get data from the parent server `load` function, but not vice versa.

### `load` dependencies

Calling `fetch(url)` inside a `load` function registers `url` as a dependency. Sometimes we can get data not through `fetch` but generate it, get it from internal storage, etc. In that case, we need to define this dependency manually by calling `depends`:

```jsx
// src/routes/+layout.js
export async function load({ depends }) {
	depends('data:now');
	return { now: Date.now() };
}

```

### Invalidation

SvelteKit has a [built-in invalidation mechanism](https://kit.svelte.dev/docs/load#rerunning-load-functions-when-do-load-functions-rerun) (e.g., it reruns when path params are changed; this mechanism [can be disabled](https://kit.svelte.dev/docs/load#rerunning-load-functions-untracking-dependencies)). However, in some cases, you need to force it and handle it manually, for example, when you need to invalidate data from the parent's load function. This can be done using the `invalidate` function, which takes a URL and reruns any `load` functions that depend on it:

```jsx
// src/routes/+page.svelte
<script>
	import { onMount } from 'svelte';
	import { invalidate } from '$app/navigation';

	export let data;

	onMount(() => {
		const interval = setInterval(() => {
			invalidate('/dynamic-data-source'); // or 'dep:foo' in case of manual deps
		}, 1000);

		return () => {
			clearInterval(interval);
		};
	});
</script>

```

- If you need to invalidate based on some pattern, you can pass a callback argument instead of a plain string: `invalidate(url => url.href.includes('foo'))`.
- `invalidateAll()` can be used to rerun all `load` functions.
    - `invalidateAll` reruns `load` functions without any URL dependencies, which `invalidate(() => true)` does not.

### Headers and Cookies

Inside a `load` function (also in [form actions](https://learn.svelte.dev/tutorial/the-form-element), [hooks](https://learn.svelte.dev/tutorial/handle), and [API routes](https://learn.svelte.dev/tutorial/get-handlers), which we'll learn about later), we have access to the `setHeaders` function:

```jsx
export function load({ setHeaders }) {
	setHeaders({
		'Content-Type': 'text/plain'
	});
}

```

**[Cookies](https://learn.svelte.dev/tutorial/cookies)**

`setHeaders` cannot be used with the `Set-Cookie` header. Instead, use `cookies`:

```jsx
export function load({ cookies }) {
	const visited = cookies.get('visited');
  cookies.set('visited', 'true', { path: '/' }); // cookies.set(name, value, options)

	return {
		visited: visited === 'true'
	};
}

```

- `cookies.set(name, ...)` causes a `Set-Cookie` header to be written and updates the internal state of cookies (using the `cookie` package under the hood).
- It is strongly recommended to configure the `path` when setting a cookie since the default behavior of browsers is to set the cookie on the parent of the current path.

## Shared Modules

If you need to share code across multiple places, the right place to keep it is the `src/lib` folder. You can access it via the `$lib` alias:

```jsx
// src/lib/message.js
export const message = 'hello from $lib/message';

// src/routes/a/deeply/nested/route/+page.svelte
<script>
	import { message } from '$lib/message.js';
</script>

<p>{message}</p>

```

That's all for now! I hope you've enjoyed and felt the real power and potential that is baked into **SvelteKit**. In the next chapter, we will overview the rest of the toolset, such as hooks, page and link tuning.

See you soon, take care, and go Svelte!

## Resources

- [SvelteKit tutorial](https://learn.svelte.dev/tutorial/introducing-sveltekit) | Introduction, Routing, Loading data, Headers and cookies, Shared modules
- [SvelteKit tutorial](https://learn.svelte.dev/tutorial/optional-params) | Advanced routing, Advanced Loading
- [SvelteKit Docs](https://kit.svelte.dev/docs/advanced-routing#advanced-layouts) | Advanced Layouts
- [Svelte Docs](https://kit.svelte.dev/docs/load) | Loading data