Load Functions
Fetching data before a page renders with +page.js, instead of fetching inside the component.
2 min de lecture
A component that fetches its own data in $effect renders once with nothing, then re-renders when the fetch resolves — a flash of empty state on every navigation, and no way to render real HTML on the server. SvelteKit's answer is the load function: data is fetched before the page renders, and handed to the component as a prop.
A minimal load function
// src/routes/blog/[slug]/+page.js
export async function load({ params, fetch }) {
const res = await fetch(`/api/posts/${params.slug}`);
const post = await res.json();
return { post };
}<!-- src/routes/blog/[slug]/+page.svelte -->
<script>
let { data } = $props();
</script>
<h1>{data.post.title}</h1>
<p>{data.post.body}</p>Whatever object a load function returns shows up as data in the matching +page.svelte — no manual wiring required. The names line up by convention: +page.js next to +page.svelte feeds it directly.
Why fetch is passed in, not imported
Notice load receives fetch as a parameter instead of using the global one. SvelteKit's fetch is special: on the server it can call relative URLs (/api/posts/1) directly without needing a full domain, and during server-side rendering it inlines the response into the initial HTML payload so the browser doesn't re-fetch the same data on hydration. Always use the provided fetch, not the global one, inside a load function.
Where load runs
A +page.js load function is "universal" — it runs on the server for the first request (as part of SSR) and again in the browser for subsequent client-side navigations. That's why it only receives things safe to run in both places: fetch, params, url. It never receives secrets like database credentials, because that code could end up running in the browser.
Reacting to the URL
load also receives the current url, useful for reading query parameters:
// src/routes/search/+page.js
export async function load({ url, fetch }) {
const query = url.searchParams.get('q') ?? '';
const res = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
return { results: await res.json(), query };
}Because load re-runs automatically whenever params or url change for the current route, navigating from /search?q=svelte to /search?q=sveltekit re-runs load and updates data — you don't need to manually watch the URL yourself.
Error handling
Throwing inside load renders the nearest error page instead of a broken component:
import { error } from '@sveltejs/kit';
export async function load({ params, fetch }) {
const res = await fetch(`/api/posts/${params.slug}`);
if (!res.ok) {
error(404, 'Post not found');
}
return { post: await res.json() };
}error() throws a special exception SvelteKit catches and turns into the right HTTP status and an +error.svelte boundary — the component itself never has to handle a "not found" case in its own markup.
The next lesson covers the other kind of load function — +page.server.js — for data that must never leave the server, like direct database queries or anything using a secret API key.