Skip to main content
Technology & EngineeringSvelte257 lines

Load Functions

SvelteKit server and universal load functions for fetching and passing data to pages and layouts

Quick Summary28 lines
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 lines
Paste into your CLAUDE.md or agent config

Load 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.all to 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/server or $env/static/private in +page.js, which fails at build time because universal load functions also run on the client. Use +page.server.js for 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 checking res.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/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().

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/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.
  • 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

Get CLI access →