mirror of
https://github.com/bretzel-app/crumbs.git
synced 2026-10-12 12:06:57 +02:00
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>
6.5 KiB
6.5 KiB
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)
// 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
envsafely - 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)
// 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
dataparameter - 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:
// +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
// +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
// +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
// +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
// +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
// +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
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
// +page.ts - WRONG
export const load = async () => {
const theme = localStorage.getItem('theme'); // ERROR on server
return { theme };
};
Fix: Check for browser
import { browser } from '$app/environment';
export const load = async () => {
const theme = browser ? localStorage.getItem('theme') : 'light';
return { theme };
};
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',
},
};
};
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