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 data permalink
A +page.svelte
file can have a sibling +page.js.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`}};}
ts
import 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.ts
file runs both on the server and in the browser (unless combined with export const ssr = false
, in which case it will only run 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.ts
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:
ts
import * asdb from '$lib/server/database';/** @type {import('./$types').PageServerLoad} */export async functionload ({params }) {return {post : awaitdb .getPost (params .slug )};}
ts
import * 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.ts
and when to use +page.server.js.ts
, see Universal vs server.
Layout data permalink
Your +layout.svelte
files can also load data, via +layout.js.ts
or +layout.server.js.ts
.
ts
import * asdb from '$lib/server/database';/** @type {import('./$types').LayoutServerLoad} */export async functionload () {return {posts : awaitdb .getPostSummaries ()};}
ts
import * 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
load
functions return data with the same key, the last one 'wins' — the result of a layoutload
returning{ a: 1, b: 2 }
and a pageload
returning{ b: 3, c: 4 }
would be{ a: 1, b: 3, c: 4 }
.
$page.data permalink
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.ts
or +page.server.js.ts
. 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 server permalink
As we've seen, there are two types of load
function:
+page.js.ts
and+layout.js.ts
files export universalload
functions that run both on the server and in the browser+page.server.js.ts
and+layout.server.js.ts
files export serverload
functions 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 run? permalink
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.
Input permalink
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.ts
and +page.server.js.ts
(or +layout.js.ts
and +layout.server.js.ts
), the return value of the server load
function is the data
property of the universal load
function's argument.
Output permalink
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. Your data can include promises, in which case it will be streamed to browsers.
When to use which permalink
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 data permalink
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
.
url permalink
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.
route permalink
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]'}
ts
import type {PageLoad } from './$types';export constload = (({route }) => {console .log (route .id ); // '/a/[b]/[...c]'}) satisfiesPageLoad ;
params permalink
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 requests permalink
To get data from an external API or a +server.js.ts
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
cookie
andauthorization
headers for the page request - it can make relative requests on the server (ordinarily,
fetch
requires a URL with an origin when used in a server context) - internal requests (e.g. for
+server.js.ts
routes) 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
text
andjson
methods of theResponse
object. 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 browserfetch
instead of theload
fetch
, this is why.
ts
/** @type {import('./$types').PageLoad} */export async functionload ({fetch ,params }) {constres = awaitfetch (`/api/items/${params .id }`);constitem = awaitres .json ();return {item };}
ts
import 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 headers permalink
A server load
function can get and set cookies
.
ts
import * asdb from '$lib/server/database';/** @type {import('./$types').LayoutServerLoad} */export async functionload ({cookies }) {constsessionid =cookies .get ('sessionid');return {user : awaitdb .getUser (sessionid )};}
ts
import * 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
path
property. By default, thepath
of a cookie is the current pathname. If you for example set a cookie at pageadmin/user
, the cookie will only be available within theadmin
pages by default. In most cases you likely want to setpath
to'/'
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 ();}
ts
import 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 data permalink
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 };}
ts
import 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 };}
ts
import 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 };}
ts
import 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
load
function in+page.js.ts
receives the merged data from both layoutload
functions, not just the immediate parent.
Inside +page.server.js.ts
and +layout.server.js.ts
, parent
returns data from parent +layout.server.js.ts
files.
In +page.js.ts
or +layout.js.ts
it will return data from parent +layout.js.ts
files. However, a missing +layout.js.ts
is treated as a ({ data }) => data
function, meaning that it will also return data from parent +layout.server.js.ts
files that are not 'shadowed' by a +layout.js.ts
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 }
};
}
Errors permalink
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:
ts
import {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');}}
ts
import {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.
Redirects permalink
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.
ts
import {redirect } from '@sveltejs/kit';/** @type {import('./$types').LayoutServerLoad} */export functionload ({locals }) {if (!locals .user ) {throwredirect (307, '/login');}}
ts
import {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 would prevent SvelteKit from handling it.
In the browser, you can also navigate programmatically outside of a load
function using goto
from $app.navigation
.
Streaming with promises permalink
Promises at the top level of the returned object will be awaited, making it easy to return multiple promises without creating a waterfall. When using a server load
, nested promises will be streamed to the browser as they resolve. This is useful if you have slow, non-essential data, since you can start rendering the page before all the data is available:
ts
/** @type {import('./$types').PageServerLoad} */export functionload () {return {one :Promise .resolve (1),two :Promise .resolve (2),streamed : {three : newPromise ((fulfil ) => {setTimeout (() => {fulfil (3)}, 1000);})}};}
ts
import type {PageServerLoad } from './$types';export constload = (() => {return {one :Promise .resolve (1),two :Promise .resolve (2),streamed : {three : newPromise ((fulfil ) => {setTimeout (() => {fulfil (3)}, 1000);})}};}) satisfiesPageServerLoad ;
This is useful for creating skeleton loading states, for example:
<script>
/** @type {import('./$types').PageData} */ export let data;
</script>
<p>
one: {data.one}
</p>
<p>
two: {data.two}
</p>
<p>
three:
{#await data.streamed.three}
Loading...
{:then value}
{value}
{:catch error}
{error.message}
{/await}
</p>
<script lang="ts">
import type { PageData } from './$types';
export let data: PageData;
</script>
<p>
one: {data.one}
</p>
<p>
two: {data.two}
</p>
<p>
three:
{#await data.streamed.three}
Loading...
{:then value}
{value}
{:catch error}
{error.message}
{/await}
</p>
On platforms that do not support streaming, such as AWS Lambda, responses will be buffered. This means the page will only render once all promises resolve.
Streaming data will only work when JavaScript is enabled. You should avoid returning nested promises from a universal
load
function if the page is server rendered, as these are not streamed — instead, the promise is recreated when the function re-runs in the browser.
Parallel loading permalink
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 functions permalink
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...
ts
import * asdb from '$lib/server/database';/** @type {import('./$types').PageServerLoad} */export async functionload ({params }) {return {post : awaitdb .getPost (params .slug )};}
ts
import * asdb from '$lib/server/database';import type {PageServerLoad } from './$types';export constload = (async ({params }) => {return {post : awaitdb .getPost (params .slug )};}) satisfiesPageServerLoad ;
ts
import * asdb from '$lib/server/database';/** @type {import('./$types').LayoutServerLoad} */export async functionload () {return {posts : awaitdb .getPostSummaries ()};}
ts
import * asdb from '$lib/server/database';import type {LayoutServerLoad } from './$types';export constload = (async () => {return {posts : awaitdb .getPostSummaries ()};}) satisfiesLayoutServerLoad ;
...the one in +page.server.js.ts
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.ts
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.
Dependency tracking does not apply after the load
function has returned — for example, accessing params.x
inside a nested promise will not cause the function to re-run when params.x
changes. (Don't worry, you'll get a warning in development if you accidentally do this.) Instead, access the parameter in the main body of your load
function.
Manual invalidation permalink
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. Server load functions will never automatically depend on a fetched url
to avoid leaking secrets to the client.
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 ()};}
ts
import 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>
When do load functions re-run? permalink
To summarize, a load
function will re-run in the following situations:
- It references a property of
params
whose value has changed - It references a property of
url
(such asurl.pathname
orurl.search
) whose value has changed. Properties inrequest.url
are not tracked - It calls
await parent()
and a parentload
function re-ran - It declared a dependency on a specific URL via
fetch
(universal load only) ordepends
, and that URL was marked invalid withinvalidate(url)
- All active
load
functions 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.