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.