Load Functions
SvelteKit server and universal load functions for fetching and passing data to pages and layouts
You are an expert in SvelteKit load functions, helping developers fetch data on the server and client, manage dependencies between loaders, and handle errors gracefully.
## Key Points
- Runs on the server during SSR (with credentials forwarded)
- Deduplicates requests between server and client on initial load
- Supports relative URLs on the server
- **Use server load for secrets and databases.** Anything touching private API keys, database connections, or `$env/static/private` must be in `+page.server.js`.
- **Use universal load for public APIs and non-serializable data.** Universal loaders can return class instances, component constructors, and functions.
- **Fetch in parallel** with `Promise.all` to minimize load times.
- **Stream non-critical data** by returning unresolved promises for secondary content.
- **Throw `error()` for expected failures** (404, 403) and let unexpected errors propagate to the error boundary.
- **Use `depends()` and `invalidate()`** for fine-grained cache control instead of `invalidateAll()`.
- **Waterfall fetches.** Awaiting parent data before starting your own fetch creates a sequential chain. Fetch in parallel when possible.
- **Accidentally running server code in universal load.** Importing from `$lib/server` in `+page.js` will fail at build time. Use `+page.server.js` for server-only imports.
- **Not handling the `fetch` response status.** Always check `res.ok` or the status before parsing the body.
## Quick Example
```js
export async function load({ fetch }) {
// Relative URL works on the server
const res = await fetch('/api/data');
return { items: await res.json() };
}
```skilldb get svelte-skills/Load FunctionsFull skill: 257 linesLoad Functions — SvelteKit
You are an expert in SvelteKit load functions, helping developers fetch data on the server and client, manage dependencies between loaders, and handle errors gracefully.
Core Philosophy
Load functions are SvelteKit's answer to the data-fetching problem: how do you get data to a page before it renders, handle both server and client rendering, avoid waterfalls, and maintain clean separation between data concerns and presentation? The answer is a dedicated function that runs before the component mounts, returning exactly the data the page needs through a typed data prop. This removes data fetching from the component lifecycle entirely.
The distinction between server load (+page.server.js) and universal load (+page.js) is an architectural boundary, not just a deployment detail. Server loads have access to databases, private environment variables, and file systems — things that must never reach the client. Universal loads run on both server and client, making them suitable for public API calls and non-serializable return values like component constructors. Choosing the wrong type does not just affect performance; it can expose secrets or break builds.
SvelteKit's dependency tracking and invalidation system turns load functions into a smart cache. The framework automatically tracks which URL properties, params, and custom dependency keys each load function accesses, and only re-runs the function when those specific dependencies change. This means navigating between sibling pages does not re-run the parent layout's load function unless it depends on something that changed. Understanding this tracking behavior is essential for avoiding both stale data and unnecessary re-fetches.
Anti-Patterns
-
Waterfall Fetches in Series — awaiting one fetch before starting another when the two are independent. Use
Promise.allto run parallel fetches and avoid sequential data-loading waterfalls that double or triple page load times. -
Server-Only Imports in Universal Load — importing from
$lib/serveror$env/static/privatein+page.js, which fails at build time because universal load functions also run on the client. Use+page.server.jsfor server-only imports. -
Returning Non-Serializable Data from Server Load — returning class instances from
+page.server.js, which cannot survive the serialization boundary between server and client. Server load return values must be serializable (SvelteKit supports Dates, Maps, Sets, and RegExps, but not arbitrary classes). -
Ignoring Fetch Response Status — calling
await fetch(url)and parsing the body without checkingres.ok, which leads to JSON parse errors on error responses instead of meaningful error handling. -
Assuming Layout Loads Re-Run on Every Navigation — expecting a layout load function to re-run when navigating between sibling pages, when it only re-runs if its tracked dependencies change. This causes stale data bugs when the layout displays data that should update.
Overview
Load functions are SvelteKit's data-fetching layer. They run before a page or layout renders, providing the component with the data it needs via the data prop. SvelteKit offers two variants: server load functions (+page.server.js / +layout.server.js) that run exclusively on the server, and universal load functions (+page.js / +layout.js) that run on the server during SSR and on the client during navigation.
Core Concepts
Server Load Functions
Defined in +page.server.js. They have access to server-only resources: databases, private environment variables, file system, etc.
// src/routes/blog/+page.server.js
import { db } from '$lib/server/database';
export async function load() {
const posts = await db.query('SELECT id, title, excerpt FROM posts ORDER BY created_at DESC');
return { posts };
}
<!-- src/routes/blog/+page.svelte -->
<script>
let { data } = $props();
</script>
{#each data.posts as post}
<article>
<h2><a href="/blog/{post.id}">{post.title}</a></h2>
<p>{post.excerpt}</p>
</article>
{/each}
Universal Load Functions
Defined in +page.js. They run on both server and client, making them suitable for calling public APIs or performing client-side data transformations.
// src/routes/blog/[slug]/+page.js
export async function load({ fetch, params }) {
const res = await fetch(`/api/posts/${params.slug}`);
if (!res.ok) throw error(res.status, 'Post not found');
const post = await res.json();
return { post };
}
The fetch Wrapper
SvelteKit provides a special fetch that:
- Runs on the server during SSR (with credentials forwarded)
- Deduplicates requests between server and client on initial load
- Supports relative URLs on the server
export async function load({ fetch }) {
// Relative URL works on the server
const res = await fetch('/api/data');
return { items: await res.json() };
}
Layout Load Functions
Layouts can have their own load functions. Their data is available to all child pages.
// src/routes/dashboard/+layout.server.js
export async function load({ locals }) {
const user = locals.user;
if (!user) redirect(303, '/login');
return { user };
}
<!-- src/routes/dashboard/+layout.svelte -->
<script>
let { data, children } = $props();
</script>
<aside>Welcome, {data.user.name}</aside>
<main>{@render children()}</main>
Child pages receive merged data from all parent layouts plus their own load function.
Using Route Parameters
// src/routes/users/[id]/+page.server.js
import { error } from '@sveltejs/kit';
export async function load({ params }) {
const user = await db.getUser(params.id);
if (!user) error(404, 'User not found');
return { user };
}
Accessing Parent Data
A child load function can access data from parent layouts:
// src/routes/dashboard/settings/+page.server.js
export async function load({ parent }) {
const { user } = await parent();
const preferences = await db.getPreferences(user.id);
return { preferences };
}
Implementation Patterns
Parallel Data Fetching
Fetch multiple resources in parallel to avoid waterfalls:
export async function load({ fetch }) {
const [postsRes, categoriesRes] = await Promise.all([
fetch('/api/posts'),
fetch('/api/categories')
]);
return {
posts: await postsRes.json(),
categories: await categoriesRes.json()
};
}
Streaming with Promises
Return nested promises to stream data — the page renders immediately with available data while slower fetches resolve.
export async function load({ fetch }) {
return {
// Awaited — blocks rendering
post: await fetch('/api/post/1').then(r => r.json()),
// NOT awaited — streamed to the client
comments: fetch('/api/post/1/comments').then(r => r.json())
};
}
<script>
let { data } = $props();
</script>
<h1>{data.post.title}</h1>
{#await data.comments}
<p>Loading comments...</p>
{:then comments}
{#each comments as comment}
<p>{comment.text}</p>
{/each}
{:catch}
<p>Failed to load comments.</p>
{/await}
Dependency Invalidation
SvelteKit tracks which URL properties a load function accesses and re-runs it when those change. You can also manually invalidate:
// This load function re-runs when params.slug changes
export async function load({ params, depends }) {
depends('app:post'); // register a custom dependency key
const post = await getPost(params.slug);
return { post };
}
<script>
import { invalidate, invalidateAll } from '$app/navigation';
async function refresh() {
await invalidate('app:post'); // re-run loaders depending on 'app:post'
// or: await invalidateAll(); // re-run ALL load functions
}
</script>
Passing Server Data to Universal Load
When both +page.server.js and +page.js exist, the server load runs first and its return value is available as data in the universal load:
// +page.server.js
export async function load() {
return { serverTime: Date.now() };
}
// +page.js
export async function load({ data }) {
return {
...data,
clientInfo: 'hydrated'
};
}
Best Practices
- Use server load for secrets and databases. Anything touching private API keys, database connections, or
$env/static/privatemust be in+page.server.js. - Use universal load for public APIs and non-serializable data. Universal loaders can return class instances, component constructors, and functions.
- Fetch in parallel with
Promise.allto minimize load times. - Stream non-critical data by returning unresolved promises for secondary content.
- Throw
error()for expected failures (404, 403) and let unexpected errors propagate to the error boundary. - Use
depends()andinvalidate()for fine-grained cache control instead ofinvalidateAll().
Common Pitfalls
- Waterfall fetches. Awaiting parent data before starting your own fetch creates a sequential chain. Fetch in parallel when possible.
- Returning non-serializable data from server load. Server load return values are serialized to JSON (with SvelteKit's devalue). Functions, Maps, Sets, Dates, and RegExps are supported, but class instances are not.
- Accidentally running server code in universal load. Importing from
$lib/serverin+page.jswill fail at build time. Use+page.server.jsfor server-only imports. - Not handling the
fetchresponse status. Always checkres.okor the status before parsing the body. - Forgetting that layout loads persist. A layout load does not re-run when navigating between sibling pages unless its tracked dependencies change. Do not assume it re-runs on every navigation.
Install this skill directly: skilldb add svelte-skills
Related Skills
Component Patterns
Svelte component composition patterns including props, snippets, context, and advanced reuse techniques
Form Actions
SvelteKit form actions for progressive enhancement with server-side form handling
Reactivity
Svelte 5 runes system for fine-grained reactivity with $state, $derived, and $effect
Stores
Svelte stores and state management patterns including writable, readable, derived, and custom stores
Sveltekit Auth
Authentication patterns in SvelteKit using hooks, cookies, sessions, and OAuth flows
Sveltekit Routing
SvelteKit file-based routing system including layouts, groups, and advanced route patterns