Fetch data before a page renders with load functions, choose server-only vs universal, read params, and stream slow data.
Why: a +page.ts file exports a load() that runs before the page renders; whatever it returns becomes the page’s data. "Universal" means it can run on the server first, then on the client during navigation. Note: use the fetch passed to load — it’s a smarter version of the browser one.
// src/routes/blog/+page.ts
import type { PageLoad } from './$types'
export const load: PageLoad = async ({ fetch }) => {
const res = await fetch('/api/posts')
const posts = await res.json()
return { posts }
}Why: the page receives whatever load returned as a data prop. Grab it with $props() and use it directly in the markup. Note: {#each} loops over a list, and the key in (post.id) helps Svelte track items.
<!-- src/routes/blog/+page.svelte -->
<script lang="ts">
let { data } = $props()
</script>
<ul>
{#each data.posts as post (post.id)}
<li>{post.title}</li>
{/each}
</ul>Why: name the file +page.server.ts and load runs ONLY on the server — so you can query a database or use secret keys without shipping them to the browser. Use this whenever data needs the server.
// src/routes/blog/+page.server.ts
import type { PageServerLoad } from './$types'
import { db } from '$lib/server/db'
export const load: PageServerLoad = async () => {
const posts = await db.getPosts() // runs on the server only
return { posts }
}Why: a load function for a dynamic route receives the params, so you can fetch exactly the right record. Note: if nothing matches, throw error(404) to show the not-found page (covered in Error Handling).
// src/routes/blog/[slug]/+page.server.ts
import type { PageServerLoad } from './$types'
import { error } from '@sveltejs/kit'
import { db } from '$lib/server/db'
export const load: PageServerLoad = async ({ params }) => {
const post = await db.getPost(params.slug)
if (!post) error(404, 'Post not found')
return { post }
}Why: return a promise instead of awaiting it, and the page renders immediately while that part streams in when ready. In the page, {#await} shows a fallback until the promise resolves. Great for one slow query that shouldn’t block everything.
// src/routes/+page.server.ts
import type { PageServerLoad } from './$types'
export const load: PageServerLoad = async () => {
return {
// Not awaited — it streams in after the page shows
comments: getComments(),
}
}<!-- src/routes/+page.svelte -->
<script lang="ts">
let { data } = $props()
</script>
{#await data.comments}
<p>Loading comments…</p>
{:then comments}
<ul>{#each comments as c (c.id)}<li>{c.text}</li>{/each}</ul>
{/await}