05 - Loading Data
Load Functions
Load functions fetch data before a page renders. The data is passed to the page component via the data prop.
Universal Load (+page.ts)
Runs on both server (first load / SSR) and client (subsequent navigation). Use for public API calls.
// src/routes/blog/+page.ts
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ fetch }) => {
const res = await fetch('/api/posts'); // use SvelteKit's fetch from the parameter
const posts = await res.json();
return { posts }; // returned object becomes `data` in the page
};SvelteKit passes its own fetch into the load function. Always use it instead of the global fetch:
SvelteKit's fetch |
Global fetch |
|
|---|---|---|
| Relative URLs | ✅ /api/posts works on server |
❌ Server doesn't know your domain |
| Cookies | ✅ Forwards cookies automatically | ❌ No cookies on server |
| Deduplication | ✅ Same request twice = one call | ❌ Makes both calls |
| SSR | ✅ Works on server and client | ⚠️ Needs full URL on server |
<!-- src/routes/blog/+page.svelte -->
<script lang="ts">
let { data } = $props(); // data = { posts: [...] } from load function
</script>
{#each data.posts as post}
<article>{post.title}</article>
{/each}Server Load (+page.server.ts)
Runs only on the server. Use when you need access to databases, secrets, or private APIs that shouldn't be exposed to the client.
// src/routes/blog/+page.server.ts
import type { PageServerLoad } from './$types';
import { db } from '$lib/server/database';
export const load: PageServerLoad = async () => {
const posts = await db.posts.findMany(); // direct DB access — server only
return { posts };
};The page component is the same — it receives data either way.
Load with Route Parameters
Access dynamic route params (e.g., [slug]) via params:
// src/routes/blog/[slug]/+page.ts
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ params, fetch }) => {
const res = await fetch(`/api/posts/${params.slug}`);
const post = await res.json();
return { post };
};Load with URL Search Params
Access query string values (e.g., ?page=2) via url:
// src/routes/blog/+page.ts
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ url }) => {
const page = url.searchParams.get('page') ?? '1';
return { page: parseInt(page) };
};Accessing Parent Layout Data
A page's load function can access data from parent layout load functions:
// src/routes/dashboard/+page.ts
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ parent }) => {
const { user } = await parent(); // get data from parent +layout.ts
return { user };
};Invalidation
Re-run load functions to refresh data without a full page reload:
<script>
import { invalidate, invalidateAll } from '$app/navigation';
function refreshPosts() {
invalidate('/api/posts'); // re-runs any load that uses this URL
}
function refreshEverything() {
invalidateAll(); // re-runs all load functions on the page
}
</script>Streaming with Promises
Return unresolved promises to stream data — the page renders immediately and fills in as data arrives:
// src/routes/blog/+page.ts
export const load = async ({ fetch }) => {
return {
posts: fetch('/api/posts').then(r => r.json()), // streams in
comments: fetch('/api/comments').then(r => r.json()) // streams in
};
};<!-- src/routes/blog/+page.svelte -->
<script>
let { data } = $props();
</script>
{#await data.posts}
<p>Loading posts...</p>
{:then posts}
{#each posts as post}
<div>{post.title}</div>
{/each}
{:catch error}
<p>Failed to load posts</p>
{/await}+page.ts vs +page.server.ts
+page.ts |
+page.server.ts |
|
|---|---|---|
| Runs on | Server + client | Server only |
| Access secrets/DB | ❌ No | ✅ Yes |
| In client bundle | ✅ Yes | ❌ No |
| Browser APIs | ✅ Yes (on client) | ❌ No |
Use +page.server.ts when you need server-only access. Use +page.ts for everything else.