+page.ts (Universal) vs +page.server.ts (Server-Only) Load
- Last updated
- Prerequisites:
- SvelteKit routing basics
- Understanding of SSR and client-side navigation
- sveltekit
- load
- universal
- server
- ssr
- data-fetching
Read at your depth
The practical view
+page.server.ts runs only on the server (during SSR and when the route is loaded on the server). +page.ts (universal load) runs on both the server and the client. Use +page.server.ts when you need direct DB access, secrets, or server-only APIs. Use +page.ts for data that can be fetched from public APIs on either side, or when you need to run code in the browser. The universal load's returned data is available in the page's $props; the server load's data is merged into the same props. Server load runs before universal load on the server (universal receives the server data as input).
The same idea in other frameworks
Next.js RSC server components + client data fetching map to server load vs universal load.Angular SSR resolvers (ResolveFn) run server-side; interceptor-based client fetching is the split.Nuxt's useAsyncData with ssr: false vs server routes mirrors the distinction.SvelteKit +page.server.ts vs +page.ts — the current topic.Legacy vs modern
Fetching in a component vs server load
Fetching inside a component couples data logic to the UI and lacks SSR serialization; server load runs before render on the server and ships the data with the page.
onMount(async () => {
data = await fetch('/api/users').then(r => r.json()); // ❌ OLD: client-only
});export const load = async ({ fetch }) => {
const res = await fetch('/api/users'); // ✅ NEW: runs on the server during SSR
return { users: await res.json() };
};Interview gotchas
Context
This tests the data-fetching architecture decision — interviewers want the security and capability boundary, not just 'server runs first'.
The mechanical answer
Use +page.server.ts when the load needs server-only capabilities: direct database access, private environment variables/secrets, cookies (reading AND writing), request headers, and server-side mutation logic. These must not be exposed to the client. Universal +page.ts runs in both environments, so it can use browser APIs and run during client-side navigation without a server round trip, but it cannot access secrets or read/write cookies. The guidance: default to server load for data that originates server-side (DB, authenticated data, CMS), and use universal load for public API data, browser-dependent computation, or when you need the same load to run during client navigation. Both serialize their results to the page; server data is merged and available to the universal load via the parent argument.
Trap
The naive answer is 'use universal load to be faster'. Universal load re-runs on every client navigation and cannot access secrets — putting sensitive logic there leaks it. Another trap: assuming server load runs on every client-side navigation — it does not; only universal load runs client-side, so data needing re-fetch must be handled by universal load or invalidation. Candidates who map capabilities to the two files show real architectural understanding.
Context
This probes the data-transfer mechanics — interviewers want to know how SvelteKit ships load data (not plain JSON) and what constraints that places on returned values.
The mechanical answer
SvelteKit serializes load data with devalue, a serializer that supports more than JSON: Date, Map, Set, RegExp, BigInt, undefined, NaN, cyclic references, and repeated references. This is why load functions can return richer structures than a JSON API would allow. The serialized payload is inlined into the SSR HTML (in a script tag) and rehydrated on the client; during client-side navigation, the server returns the same payload JSON for server loads, and universal load runs locally. The constraint is the opposite of JSON: functions, promises, and class instances (with methods) cannot be serialized — return plain data and plain objects. Also, a top-level promise in the return triggers SvelteKit's streaming (await it or accept the partial-hydration streaming behavior).
Trap
The naive answer is 'load data is sent as JSON'. It uses devalue, which handles richer types. Another trap: returning class instances or functions from load — they break serialization and produce errors or lose identity. Candidates who mention devalue and the non-serializable types show real under-the-hood knowledge.