Files
crumbs/.agents/skills/sveltekit-data-flow/references/load-functions.md
T
Frederic CabassutandClaude Opus 4.6 02e6b57764 chore: add agent skills for Svelte 5, SvelteKit, TypeScript, and Drizzle
Install project-level skills via skills.sh CLI:
- svelte5-best-practices (runes, snippets, events, migration)
- sveltekit-structure (routing, layouts, error boundaries)
- sveltekit-data-flow (load functions, form actions, serialization)
- typescript-best-practices (type-first development, discriminated unions)
- drizzle-migrations (SQLite schema, query patterns)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 14:11:36 +01:00

293 lines
6.5 KiB
Markdown

# Load Functions: Server vs Universal
## Decision Matrix
| Need | Use | File |
| ----------------------------------- | -------------- | ----------------- |
| Database access | Server load | `+page.server.ts` |
| Secrets/env vars | Server load | `+page.server.ts` |
| Server-only packages | Server load | `+page.server.ts` |
| Browser APIs (window, localStorage) | Universal load | `+page.ts` |
| Client-side fetch | Universal load | `+page.ts` |
| Runs on both | Universal load | `+page.ts` |
## Server Load (+page.server.ts)
**When:** Need server-only resources (DB, secrets, server APIs)
**Runs:** Only on server (never in browser)
```typescript
// src/routes/profile/+page.server.ts
import type { PageServerLoad } from './$types';
import { db } from '$lib/server/database';
export const load: PageServerLoad = async ({ locals, params }) => {
// Access server-only resources
const user = await db.query.users.findFirst({
where: eq(users.id, locals.userId),
});
const posts = await db.query.posts.findMany({
where: eq(posts.authorId, user.id),
});
// Must return serializable data
return {
user: {
id: user.id,
name: user.name,
email: user.email,
},
posts,
};
};
```
**Key points:**
- Runs only on server
- Can access `$lib/server/*` imports
- Can use secrets from `env` safely
- Return values must be JSON-serializable
- Output is automatically passed to universal load
## Universal Load (+page.ts)
**When:** Need to run on both server and client, or need browser APIs
**Runs:** Server (during SSR) AND client (during navigation)
```typescript
// src/routes/dashboard/+page.ts
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ data, fetch }) => {
// `data` comes from +page.server.ts if it exists
const { user } = data;
// Fetch additional data (works on both server and client)
const response = await fetch('/api/stats');
const stats = await response.json();
// Can access browser APIs (but check if in browser first)
const theme =
typeof window !== 'undefined'
? localStorage.getItem('theme')
: null;
return {
user,
stats,
theme,
};
};
```
**Key points:**
- Runs on both server AND client
- Receives server load output as `data` parameter
- Use SvelteKit's `fetch` (automatically handles SSR)
- Check `typeof window !== 'undefined'` for browser APIs
- Cannot import from `$lib/server/*`
## Data Flow
```
Request → Server Load (+page.server.ts)
↓ (returns { user })
Universal Load (+page.ts)
↓ (receives data: { user }, returns { user, stats })
Page Component (+page.svelte)
↓ (receives data: { user, stats })
```
**Example:**
```typescript
// +page.server.ts
export const load = async () => {
return { serverData: 'from server' };
};
// +page.ts
export const load = async ({ data }) => {
console.log(data.serverData); // 'from server'
return { ...data, clientData: 'from universal' };
};
// +page.svelte
<script>
export let data; // { serverData, clientData }
</script>
```
## Common Patterns
### Pattern 1: Server + Universal
```typescript
// +page.server.ts - Fetch sensitive data
export const load = async ({ locals }) => {
const user = await getUser(locals.session);
return { user };
};
// +page.ts - Fetch public data
export const load = async ({ data, fetch }) => {
const publicPosts = await fetch('/api/posts').then((r) => r.json());
return { ...data, publicPosts };
};
```
### Pattern 2: Conditional Universal Load
```typescript
// +page.ts
import { browser } from '$app/environment';
export const load = async ({ fetch }) => {
const serverData = await fetch('/api/data').then((r) => r.json());
// Only run in browser
let clientOnlyData = null;
if (browser) {
clientOnlyData = localStorage.getItem('cache');
}
return { serverData, clientOnlyData };
};
```
### Pattern 3: Depends for Revalidation
```typescript
// +page.ts
export const load = async ({ fetch, depends }) => {
depends('app:posts'); // Invalidate with invalidate('app:posts')
const posts = await fetch('/api/posts').then((r) => r.json());
return { posts };
};
// Somewhere else:
import { invalidate } from '$app/navigation';
invalidate('app:posts'); // Re-runs load function
```
## Common Mistakes
### ❌ Importing Server Code in Universal Load
```typescript
// +page.ts - WRONG
import { db } from '$lib/server/database'; // ERROR - can't import server code
export const load = async () => {
const users = await db.query.users.findMany(); // Won't work
return { users };
};
```
**Fix:** Move to `+page.server.ts`
### ❌ Returning Non-Serializable Data
```typescript
// +page.server.ts - WRONG
export const load = async () => {
const user = await User.findOne(); // Returns class instance
return { user }; // ERROR - class instances aren't serializable
};
```
**Fix:** Return plain objects
```typescript
export const load = async () => {
const user = await User.findOne();
return {
user: {
id: user.id,
name: user.name,
email: user.email,
},
};
};
```
### ❌ Using window/localStorage Without Check
```typescript
// +page.ts - WRONG
export const load = async () => {
const theme = localStorage.getItem('theme'); // ERROR on server
return { theme };
};
```
**Fix:** Check for browser
```typescript
import { browser } from '$app/environment';
export const load = async () => {
const theme = browser ? localStorage.getItem('theme') : 'light';
return { theme };
};
```
## TypeScript
```typescript
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({
locals,
params,
url,
}) => {
// TypeScript knows return type must be serializable
return {
user: {
id: 1,
name: 'Alice',
},
};
};
```
```typescript
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ data, fetch, params }) => {
// `data` is typed from +page.server.ts return type
return {
...data,
extra: 'data',
};
};
```
## When to Use Which
**Use Server Load when:**
- Need database access
- Need secrets/environment variables
- Need server-only npm packages
- Need to hide implementation details
**Use Universal Load when:**
- Need browser APIs (localStorage, window)
- Need to fetch public APIs (works on both)
- Want client-side navigation without server round-trip
- Data is public and doesn't need server resources
**Use Both when:**
- Server load fetches sensitive data
- Universal load fetches public data or adds client-side enhancements