Skip to main content
Before a +page.svelte component (and its containing +layout.svelte components) can be rendered, we often need to get some data. This is done by defining load functions.

Page data

A +page.svelte file can have a sibling +page.js that exports a load function:
/// file: src/routes/blog/[slug]/+page.js
/** @type {import('./$types').PageLoad} */
export function load({ params }) {
	return {
		post: {
			title: `Title for ${params.slug} goes here`,
			content: `Content for ${params.slug} goes here`
		}
	};
}
Thanks to the generated $types module, we get full type safety.

Universal vs server load functions

There are two types of load function:
Files: +page.js and +layout.js
  • Run both on the server and in the browser
  • Can return any values, including component constructors
  • Useful for fetching from external APIs
/// file: src/routes/items/[id]/+page.js
/** @type {import('./$types').PageLoad} */
export async function load({ fetch, params }) {
	const res = await fetch(`/api/items/${params.id}`);
	const item = await res.json();

	return { item };
}

When does which load function run?

1

Server load functions

Always run on the server.
2

Universal load functions

By default, run on the server during SSR when the user first visits your page. They will then run again during hydration, reusing any responses from fetch requests.
3

Concurrent execution

If a route contains both universal and server load functions, the server load runs first.

Layout data

Your +layout.svelte files can also load data via +layout.js or +layout.server.js:
/// file: src/routes/blog/[slug]/+layout.server.js
import * as db from '$lib/server/database';

/** @type {import('./$types').LayoutServerLoad} */
export async function load() {
	return {
		posts: await db.getPostSummaries()
	};
}
Data returned from layout load functions is available to child +layout.svelte components and the +page.svelte component.

Using URL data

The load function provides access to URL information:
An instance of URL, containing properties like origin, hostname, pathname and searchParams:
export function load({ url }) {
	const query = url.searchParams.get('q');
	// ...
}
Contains the name of the current route directory:
/// file: src/routes/a/[b]/[...c]/+page.js
export function load({ route }) {
	console.log(route.id); // '/a/[b]/[...c]'
}
Derived from url.pathname and route.id:
// Given route.id of '/a/[b]/[...c]' 
// and url.pathname of '/a/x/y/z'
export function load({ params }) {
	console.log(params); // { b: 'x', c: 'y/z' }
}

Making fetch requests

The provided fetch function has special features:

Credentialed requests

Inherits cookie and authorization headers on the server

Relative requests

Can make relative requests on the server

Direct routing

Internal requests go directly to handler function

Response inlining

Responses are captured and inlined into rendered HTML during SSR
/// file: src/routes/items/[id]/+page.js
/** @type {import('./$types').PageLoad} */
export async function load({ fetch, params }) {
	const res = await fetch(`/api/items/${params.id}`);
	const item = await res.json();

	return { item };
}

Working with cookies

A server load function can get and set cookies:
/// file: src/routes/+layout.server.js
import * as db from '$lib/server/database';

/** @type {import('./$types').LayoutServerLoad} */
export async function load({ cookies }) {
	const sessionid = cookies.get('sessionid');

	return {
		user: await db.getUser(sessionid)
	};
}
Cookies will only be passed through the provided fetch function if the target host is the same as the SvelteKit application or a more specific subdomain of it.

Setting headers

Both server and universal load functions have access to a setHeaders function:
/// file: src/routes/products/+page.js
/** @type {import('./$types').PageLoad} */
export async function load({ fetch, setHeaders }) {
	const url = `https://cms.example.com/products.json`;
	const response = await fetch(url);

	// Cache the page for the same length of time as the underlying data
	setHeaders({
		age: response.headers.get('age'),
		'cache-control': response.headers.get('cache-control')
	});

	return response.json();
}
Setting the same header multiple times is an error. You can only set a given header once using the setHeaders function.

Using parent data

A load function can access data from a parent load function with await parent():
1

Root layout

/// file: src/routes/+layout.js
export function load() {
	return { a: 1 };
}
2

Child layout

/// file: src/routes/abc/+layout.js
export async function load({ parent }) {
	const { a } = await parent();
	return { b: a + 1 };
}
3

Page

/// file: src/routes/abc/+page.js
export async function load({ parent }) {
	const { a, b } = await parent();
	return { c: a + b };
}
Take care not to introduce waterfalls when using await parent(). Call independent data fetches first to avoid delayed renders.

Error handling

If an error is thrown during load, the nearest +error.svelte will be rendered:
/// file: src/routes/admin/+layout.server.js
import { error } from '@sveltejs/kit';

/** @type {import('./$types').LayoutServerLoad} */
export function load({ locals }) {
	if (!locals.user) {
		error(401, 'not logged in');
	}

	if (!locals.user.isAdmin) {
		error(403, 'not an admin');
	}
}

Redirects

To redirect users, use the redirect helper:
/// file: src/routes/user/+layout.server.js
import { redirect } from '@sveltejs/kit';

/** @type {import('./$types').LayoutServerLoad} */
export function load({ locals }) {
	if (!locals.user) {
		redirect(307, '/login');
	}
}
In the browser, you can also navigate programmatically outside of a load function using goto from $app/navigation.

Streaming with promises

When using a server load, promises will be streamed to the browser as they resolve:
/// file: src/routes/blog/[slug]/+page.server.js
/** @type {import('./$types').PageServerLoad} */
export async function load({ params }) {
	return {
		// Load comments without blocking post
		comments: loadComments(params.slug),
		post: await loadPost(params.slug)
	};
}
This enables skeleton loading states:
<!--- file: src/routes/blog/[slug]/+page.svelte --->
<script>
	/** @type {import('./$types').PageProps} */
	let { data } = $props();
</script>

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

{#await data.comments}
	Loading comments...
{:then comments}
	{#each comments as comment}
		<p>{comment.content}</p>
	{/each}
{:catch error}
	<p>error loading comments: {error.message}</p>
{/await}
On platforms that do not support streaming (like AWS Lambda), responses will be buffered. The page will only render once all promises resolve.

Rerunning load functions

SvelteKit tracks dependencies to avoid rerunning load functions unnecessarily.

When do load functions rerun?

A load function will rerun when:
It references a property of params whose value has changed
It references a property of url (like url.pathname or url.search) whose value has changed
It calls url.searchParams.get(...) and the parameter in question changes
It calls await parent() and a parent load function reran
A URL it depends on was marked invalid with invalidate(url) or all functions were rerun with invalidateAll()

Manual invalidation

You can rerun load functions using:
import { invalidate } from '$app/navigation';

// Reruns all load functions that depend on this URL
invalidate('https://api.example.com/data');

// Or use custom identifiers
invalidate('app:data');

Authentication implications

Important considerations for auth checks:
Layout load functions do not run on every request during client-side navigation between child routes.
Recommended strategies:
1

Use hooks

Protect multiple routes before any load functions run using server hooks
2

Page-level guards

Use auth guards directly in +page.server.js for route-specific protection
3

Avoid layout guards

Putting auth guards in +layout.server.js requires all child pages to call await parent() before protected code

Next steps