Loading data
Edit this page on GitHubBefore 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 datapermalink
A +page.svelte file can have a sibling +page.js (or +page.ts) that exports a load function, the return value of which is available to the page via the data prop:
ts/** @type {import('./$types').PageLoad} */export functionload ({params }) {return {post : {title : `Title for ${params .slug } goes here`,content : `Content for ${params .slug } goes here`}};}
tsimport type {PageLoad } from './$types';export constload = (({params }) => {return {post : {title : `Title for ${params .slug } goes here`,content : `Content for ${params .slug } goes here`}};}) satisfiesPageLoad ;
<script>
/** @type {import('./$types').PageData} */ export let data;
</script>
<h1>{data.post.title}</h1>
<div>{@html data.post.content}</div><script lang="ts">
import type { PageData } from './$types';
export let data: PageData;
</script>
<h1>{data.post.title}</h1>
<div>{@html data.post.content}</div>Thanks to the generated $types module, we get full type safety.
A load function in a +page.js file runs both on the server and in the browser. If your load function should always run on the server (because it uses private environment variables, for example, or accesses a database) then it would go in a +page.server.js instead.
A more realistic version of your blog post's load function, that only runs on the server and pulls data from a database, might look like this:
tsimport * asdb from '$lib/server/database';/** @type {import('./$types').PageServerLoad} */export async functionload ({params }) {return {post : awaitdb .getPost (params .slug )};}
tsimport * asdb from '$lib/server/database';import type {PageServerLoad } from './$types';export constload = (async ({params }) => {return {post : awaitdb .getPost (params .slug )};}) satisfiesPageServerLoad ;
Notice that the type changed from PageLoad to PageServerLoad, because server load functions can access additional arguments. To understand when to use +page.js and when to use +page.server.js, see Universal vs server.
Layout datapermalink
Your +layout.svelte files can also load data, via +layout.js or +layout.server.js.
tsimport * asdb from '$lib/server/database';/** @type {import('./$types').LayoutServerLoad} */export async functionload () {return {posts : awaitdb .getPostSummaries ()};}
tsimport * asdb from '$lib/server/database';import type {LayoutServerLoad } from './$types';export constload = (async () => {return {posts : awaitdb .getPostSummaries ()};}) satisfiesLayoutServerLoad ;
<script>
/** @type {import('./$types').LayoutData} */ export let data;
</script>
<main>
<!-- +page.svelte is rendered in this <slot> --> <slot />
</main>
<aside>
<h2>More posts</h2>
<ul>
{#each data.posts as post}
<li>
<a href="/blog/{post.slug}">
{post.title}
</a>
</li>
{/each}
</ul>
</aside><script lang="ts">
import type { LayoutData } from './$types';
export let data: LayoutData;
</script>
<main>
<!-- +page.svelte is rendered in this <slot> --> <slot />
</main>
<aside>
<h2>More posts</h2>
<ul>
{#each data.posts as post}
<li>
<a href="/blog/{post.slug}">
{post.title}
</a>
</li>
{/each}
</ul>
</aside>Data returned from layout load functions is available to child +layout.svelte components and the +page.svelte component as well as the layout that it 'belongs' to.
<script>
import { page } from '$app/stores';
/** @type {import('./$types').PageData} */
export let data;
// we can access `data.posts` because it's returned from
// the parent layout `load` function
$: index = data.posts.findIndex(post => post.slug === $page.params.slug);
$: next = data.posts[index - 1];
</script>
<h1>{data.post.title}</h1>
<div>{@html data.post.content}</div>
{#if next}
<p>Next post: <a href="/blog/{next.slug}">{next.title}</a></p>
{/if}
If multiple
loadfunctions return data with the same key, the last one 'wins' — the result of a layoutloadreturning{ a: 1, b: 2 }and a pageloadreturning{ b: 3, c: 4 }would be{ a: 1, b: 3, c: 4 }.
$page.datapermalink
The +page.svelte component, and each +layout.svelte component above it, has access to its own data plus all the data from its parents.
In some cases, we might need the opposite — a parent layout might need to access page data or data from a child layout. For example, the root layout might want to access a title property returned from a load function in +page.js or +page.server.js. This can be done with $page.data:
<script>
import { page } from '$app/stores';
</script>
<svelte:head>
<title>{$page.data.title}</title>
</svelte:head>Type information for $page.data is provided by App.PageData.
Universal vs serverpermalink
As we've seen, there are two types of load function:
+page.jsand+layout.jsfiles export universalloadfunctions that run both on the server and in the browser+page.server.jsand+layout.server.jsfiles export serverloadfunctions that only run server-side
Conceptually, they're the same thing, but there are some important differences to be aware of.
When does which load function runpermalink
Server load functions always run on the server.
By default universal load functions 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. All subsequent invocations of universal load functions happen in the browser. You can customize the behavior through page options. If you disable server side rendering, you'll get an SPA and universal load functions always run on the client.
A load function is invoked at runtime, unless you prerender the page — in that case, it's invoked at build time.
Inputpermalink
Both universal and server load functions have access to properties describing the request (params, route and url) and various functions (fetch, setHeaders, parent and depends). These are described in the following sections.
Server load functions are called with a ServerLoadEvent, which inherits clientAddress, cookies, locals, platform and request from RequestEvent.
Universal load functions are called with a LoadEvent, which has a data property. If you have load functions in both +page.js and +page.server.js (or +layout.js and +layout.server.js), the return value of the server load function is the data property of the universal load function's argument.
Outputpermalink
A universal load function can return an object containing any values, including things like custom classes and component constructors.
A server load function must return data that can be serialized with devalue — anything that can be represented as JSON plus things like BigInt, Date, Map, Set and RegExp, or repeated/cyclical references — so that it can be transported over the network.
When to use whichpermalink
Server load functions are convenient when you need to access data directly from a database or filesystem, or need to use private environment variables.
Universal load functions are useful when you need to fetch data from an external API and don't need private credentials, since SvelteKit can get the data directly from the API rather than going via your server. They are also useful when you need to return something that can't be serialized, such as a Svelte component constructor.
In rare cases, you might need to use both together — for example, you might need to return an instance of a custom class that was initialised with data from your server.
Using URL datapermalink
Often the load function depends on the URL in one way or another. For this, the load function provides you with url, route and params.
urlpermalink
An instance of URL, containing properties like the origin, hostname, pathname and searchParams (which contains the parsed query string as a URLSearchParams object). url.hash cannot be accessed during load, since it is unavailable on the server.
In some environments this is derived from request headers during server-side rendering. If you're using adapter-node, for example, you may need to configure the adapter in order for the URL to be correct.
routepermalink
Contains the name of the current route directory, relative to src/routes:
ts/** @type {import('./$types').PageLoad} */export functionload ({route }) {console .log (route .id ); // '/a/[b]/[...c]'}
tsimport type {PageLoad } from './$types';export constload = (({route }) => {console .log (route .id ); // '/a/[b]/[...c]'}) satisfiesPageLoad ;
paramspermalink
params is derived from url.pathname and route.id.
Given a route.id of /a/[b]/[...c] and a url.pathname of /a/x/y/z, the params object would look like this:
{
"b": "x",
"c": "y/z"
}Making fetch requestspermalink
To get data from an external API or a +server.js handler, you can use the provided fetch function, which behaves identically to the native fetch web API with a few additional features:
- it can be used to make credentialed requests on the server, as it inherits the
cookieandauthorizationheaders for the page request - it can make relative requests on the server (ordinarily,
fetchrequires a URL with an origin when used in a server context) - internal requests (e.g. for
+server.jsroutes) go direct to the handler function when running on the server, without the overhead of an HTTP call - during server-side rendering, the response will be captured and inlined into the rendered HTML by hooking into the
textandjsonmethods of theResponseobject. Note that headers will not be serialized, unless explicitly included viafilterSerializedResponseHeaders. Then, during hydration, the response will be read from the HTML, guaranteeing consistency and preventing an additional network request - if you got a warning in your browser console when using the browserfetchinstead of theloadfetch, this is why.
ts/** @type {import('./$types').PageLoad} */export async functionload ({fetch ,params }) {constres = awaitfetch (`/api/items/${params .id }`);constitem = awaitres .json ();return {item };}
tsimport type {PageLoad } from './$types';export constload = (async ({fetch ,params }) => {constres = awaitfetch (`/api/items/${params .id }`);constitem = awaitres .json ();return {item };}) satisfiesPageLoad ;
Cookies will only be passed through if the target host is the same as the SvelteKit application or a more specific subdomain of it.
Cookies and headerspermalink
A server load function can get and set cookies.
tsimport * asdb from '$lib/server/database';/** @type {import('./$types').LayoutServerLoad} */export async functionload ({cookies }) {constsessionid =cookies .get ('sessionid');return {user : awaitdb .getUser (sessionid )};}
tsimport * asdb from '$lib/server/database';import type {LayoutServerLoad } from './$types';export constload = (async ({cookies }) => {constsessionid =cookies .get ('sessionid');return {user : awaitdb .getUser (sessionid )};}) satisfiesLayoutServerLoad ;
When setting cookies, be aware of the
pathproperty. By default, thepathof a cookie is the current pathname. If you for example set a cookie at pageadmin/user, the cookie will only be available within theadminpages by default. In most cases you likely want to setpathto'/'to make the cookie available throughout your app.
Both server and universal load functions have access to a setHeaders function that, when running on the server, can set headers for the response. (When running in the browser, setHeaders has no effect.) This is useful if you want the page to be cached, for example:
ts/** @type {import('./$types').PageLoad} */export async functionload ({fetch ,setHeaders }) {consturl = `https://cms.example.com/products.json`;constresponse = awaitfetch (url );// cache the page for the same length of time// as the underlying datasetHeaders ({age :response .headers .get ('age'),'cache-control':response .headers .get ('cache-control')});returnresponse .json ();}
tsimport type {PageLoad } from './$types';export constload = (async ({fetch ,setHeaders }) => {consturl = `https://cms.example.com/products.json`;constresponse = awaitfetch (url );// cache the page for the same length of time// as the underlying datasetHeaders ({age :response .headers .get ('age'),'cache-control':response .headers .get ('cache-control')});returnresponse .json ();}) satisfiesPageLoad ;
Setting the same header multiple times (even in separate load functions) is an error — you can only set a given header once. You cannot add a set-cookie header with setHeaders — use cookies.set(name, value, options) instead.
Using parent datapermalink
Occasionally it's useful for a load function to access data from a parent load function, which can be done with await parent():
ts/** @type {import('./$types').LayoutLoad} */export functionload () {return {a : 1 };}
tsimport type {LayoutLoad } from './$types';export constload = (() => {return {a : 1 };}) satisfiesLayoutLoad ;
ts/** @type {import('./$types').LayoutLoad} */export async functionload ({parent }) {const {a } = awaitparent ();return {b :a + 1 };}
tsimport type {LayoutLoad } from './$types';export constload = (async ({parent }) => {const {a } = awaitparent ();return {b :a + 1 };}) satisfiesLayoutLoad ;
ts/** @type {import('./$types').PageLoad} */export async functionload ({parent }) {const {a ,b } = awaitparent ();return {c :a +b };}
tsimport type {PageLoad } from './$types';export constload = (async ({parent }) => {const {a ,b } = awaitparent ();return {c :a +b };}) satisfiesPageLoad ;
<script>
/** @type {import('./$types').PageData} */ export let data;
</script>
<!-- renders `1 + 2 = 3` --><p>{data.a} + {data.b} = {data.c}</p><script lang="ts">
import type { PageData } from './$types';
export let data: PageData;
</script>
<!-- renders `1 + 2 = 3` --><p>{data.a} + {data.b} = {data.c}</p>Notice that the
loadfunction in+page.jsreceives the merged data from both layoutloadfunctions, not just the immediate parent.
Inside +page.server.js and +layout.server.js, parent returns data from parent +layout.server.js files.
In +page.js or +layout.js it will return data from parent +layout.js files. However, a missing +layout.js is treated as a ({ data }) => data function, meaning that it will also return data from parent +layout.server.js files that are not 'shadowed' by a +layout.js file
Take care not to introduce waterfalls when using await parent(). Here, for example, getData(params) does not depend on the result of calling parent(), so we should call it first to avoid a delayed render.
/** @type {import('./$types').PageLoad} */
export async function load({ params, parent }) {
const parentData = await parent();
const data = await getData(params);
const parentData = await parent();
return {
...data
meta: { ...parentData.meta, ...data.meta }
};
}
Errorspermalink
If an error is thrown during load, the nearest +error.svelte will be rendered. For expected errors, use the error helper from @sveltejs/kit to specify the HTTP status code and an optional message:
tsimport {error } from '@sveltejs/kit';/** @type {import('./$types').LayoutServerLoad} */export functionload ({locals }) {if (!locals .user ) {throwerror (401, 'not logged in');}if (!locals .user .isAdmin ) {throwerror (403, 'not an admin');}}
tsimport {error } from '@sveltejs/kit';import type {LayoutServerLoad } from './$types';export constload = (({locals }) => {if (!locals .user ) {throwerror (401, 'not logged in');}if (!locals .user .isAdmin ) {throwerror (403, 'not an admin');}}) satisfiesLayoutServerLoad ;
If an unexpected error is thrown, SvelteKit will invoke handleError and treat it as a 500 Internal Error.
Redirectspermalink
To redirect users, use the redirect helper from @sveltejs/kit to specify the location to which they should be redirected alongside a 3xx status code.
tsimport {redirect } from '@sveltejs/kit';/** @type {import('./$types').LayoutServerLoad} */export functionload ({locals }) {if (!locals .user ) {throwredirect (307, '/login');}}
tsimport {redirect } from '@sveltejs/kit';import type {LayoutServerLoad } from './$types';export constload = (({locals }) => {if (!locals .user ) {throwredirect (307, '/login');}}) satisfiesLayoutServerLoad ;
Make sure you're not catching the thrown redirect, which results in a noop.
In the browser, you can also navigate programmatically outside of a load function using goto from $app.navigation.
Promise unwrappingpermalink
Top-level promises will be awaited, which makes it easy to return multiple promises without creating a waterfall:
ts/** @type {import('./$types').PageLoad} */export functionload () {return {a :Promise .resolve ('a'),b :Promise .resolve ('b'),c : {value :Promise .resolve ('c')}};}
tsimport type {PageLoad } from './$types';export constload = (() => {return {a :Promise .resolve ('a'),b :Promise .resolve ('b'),c : {value :Promise .resolve ('c')}};}) satisfiesPageLoad ;
<script>
/** @type {import('./$types').PageData} */ export let data;
console.log(data.a); // 'a'
console.log(data.b); // 'b'
console.log(data.c.value); // `Promise {...}`
</script><script lang="ts">
import type { PageData } from './$types';
export let data: PageData;
console.log(data.a); // 'a'
console.log(data.b); // 'b'
console.log(data.c.value); // `Promise {...}`
</script>Parallel loadingpermalink
When rendering (or navigating to) a page, SvelteKit runs all load functions concurrently, avoiding a waterfall of requests. During client-side navigation, the result of calling multiple server load functions are grouped into a single response. Once all load functions have returned, the page is rendered.
Rerunning load functionspermalink
SvelteKit tracks the dependencies of each load function to avoid re-running it unnecessarily during navigation.
For example, given a pair of load functions like these...
tsimport * asdb from '$lib/server/database';/** @type {import('./$types').PageServerLoad} */export async functionload ({params }) {return {post : awaitdb .getPost (params .slug )};}
tsimport * asdb from '$lib/server/database';import type {PageServerLoad } from './$types';export constload = (async ({params }) => {return {post : awaitdb .getPost (params .slug )};}) satisfiesPageServerLoad ;
tsimport * asdb from '$lib/server/database';/** @type {import('./$types').LayoutServerLoad} */export async functionload () {return {posts : awaitdb .getPostSummaries ()};}
tsimport * asdb from '$lib/server/database';import type {LayoutServerLoad } from './$types';export constload = (async () => {return {posts : awaitdb .getPostSummaries ()};}) satisfiesLayoutServerLoad ;
...the one in +page.server.js will re-run if we navigate from /blog/trying-the-raw-meat-diet to /blog/i-regret-my-choices because params.slug has changed. The one in +layout.server.js will not, because the data is still valid. In other words, we won't call db.getPostSummaries() a second time.
A load function that calls await parent() will also re-run if a parent load function is re-run.
Manual invalidationpermalink
You can also re-run load functions that apply to the current page using invalidate(url), which re-runs all load functions that depend on url, and invalidateAll(), which re-runs every load function.
A load function depends on url if it calls fetch(url) or depends(url). Note that url can be a custom identifier that starts with [a-z]::
ts/** @type {import('./$types').PageLoad} */export async functionload ({fetch ,depends }) {// load reruns when `invalidate('https://api.example.com/random-number')` is called...constresponse = awaitfetch ('https://api.example.com/random-number');// ...or when `invalidate('app:random')` is calleddepends ('app:random');return {number : awaitresponse .json ()};}
tsimport type {PageLoad } from './$types';export constload = (async ({fetch ,depends }) => {// load reruns when `invalidate('https://api.example.com/random-number')` is called...constresponse = awaitfetch ('https://api.example.com/random-number');// ...or when `invalidate('app:random')` is calleddepends ('app:random');return {number : awaitresponse .json ()};}) satisfiesPageLoad ;
<script>
import { invalidate, invalidateAll } from '$app/navigation';
/** @type {import('./$types').PageData} */ export let data;
function rerunLoadFunction() {
// any of these will cause the `load` function to re-run invalidate('app:random');
invalidate('https://api.example.com/random-number');
invalidate(url => url.href.includes('random-number'));
invalidateAll();
}
</script>
<p>random number: {data.number}</p>
<button on:click={rerunLoadFunction}>Update random number</button><script lang="ts">
import { invalidate, invalidateAll } from '$app/navigation';
import type { PageData } from './$types';
export let data: PageData;
function rerunLoadFunction() {
// any of these will cause the `load` function to re-run invalidate('app:random');
invalidate('https://api.example.com/random-number');
invalidate(url => url.href.includes('random-number'));
invalidateAll();
}
</script>
<p>random number: {data.number}</p>
<button on:click={rerunLoadFunction}>Update random number</button>To summarize, a load function will re-run in the following situations:
- It references a property of
paramswhose value has changed - It references a property of
url(such asurl.pathnameorurl.search) whose value has changed - It calls
await parent()and a parentloadfunction re-ran - It declared a dependency on a specific URL via
fetchordepends, and that URL was marked invalid withinvalidate(url) - All active
loadfunctions were forcibly re-run withinvalidateAll()
params and url can change in response to a <a href=".."> link click, a <form> interaction, a goto invocation, or a redirect.
Note that re-running a load function will update the data prop inside the corresponding +layout.svelte or +page.svelte; it does not cause the component to be recreated. As a result, internal state is preserved. If this isn't what you want, you can reset whatever you need to reset inside an afterNavigate callback, and/or wrap your component in a {#key ...} block.