SvelteKit Preloading and Load Functions
This guide is part of Astro & SvelteKit Performance, within Framework Performance. In SvelteKit, each route can have load functions that fetch the data its page needs: +page.server.ts (runs only on the server), +page.ts (universal: runs on the server for the first request and in the browser for client-side navigations), and layout loads that provide shared data. The router runs these loads before rendering a route, both on first load (server-side) and on navigation.
Two things determine how fast this feels. First, load function design: sequential awaits and parent dependencies create waterfalls that add latency to every page. Second, preloading: SvelteKit can preload a route's code and data when a user hovers, taps, or a link enters the viewport, so navigation starts with work already done. Together, well-designed loads and preloading make most navigations feel instant.
Rapid Diagnosis
- Check the root layout or body for
data-sveltekit-preload-dataanddata-sveltekit-preload-codeattributes. - Read load functions for sequential
awaits andawait parent()calls. - Record navigation timing in DevTools: time from click to the new page rendering.
- Check server load TTFB for first requests on key routes.
Root Cause Analysis
1. Waterfalls. Each await waits for the previous one.
2. Parent dependencies. await parent() at the start blocks on layout data.
3. Preloading disabled. Navigations start fetching only on click.
4. Slow, uncached data sources. Every load hits a slow API.
Step-by-Step Resolution
1. Parallelise independent loads
// +page.server.ts
export async function load({ params, fetch }) {
const [product, stock, related] = await Promise.all([
fetch(`/api/products/${params.id}`).then((r) => r.json()),
fetch(`/api/stock/${params.id}`).then((r) => r.json()),
fetch(`/api/related/${params.id}`).then((r) => r.json()),
]);
return { product, stock, related };
}
// trade-off: Promise.all fails if any request fails; use allSettled or handle
// errors per request when partial pages are acceptable.
2. Stream non-critical data
Return promises for non-essential data without awaiting them; SvelteKit streams them to the page, which renders them with {#await} blocks.
export async function load({ params, fetch }) {
const product = await fetch(`/api/products/${params.id}`).then((r) => r.json());
return { product, reviews: fetch(`/api/reviews/${params.id}`).then((r) => r.json()) }; // streamed
}
3. Enable preloading
<!-- src/app.html -->
<body data-sveltekit-preload-data="hover" data-sveltekit-preload-code="viewport">%sveltekit.body%</body>
4. Cache server data
Set Cache-Control headers with setHeaders in load functions for cacheable data, cache API responses at the CDN, and prerender static routes.
Verification
Hover over a link and check the Network panel: the route's code and data (__data.json) should load before the click. Navigation after clicking should render almost immediately. For first loads, TTFB should drop after parallelising and caching. In the field, track LCP for first loads and timings for client navigations.
Worked Example: An Online Course Platform
A course platform's lesson pages loaded the course (layout load), then the lesson, then the user's progress, then comments — four sequential requests, with await parent() at the start of each page load. Navigating between lessons took 900ms. The team parallelised lesson and progress fetches, streamed comments as a promise, removed unnecessary await parent() calls by fetching only what each load needed, and enabled preload-data="hover". Navigation between lessons dropped to under 150ms (often instant after hover), and first-load TTFB fell from 820ms to 340ms.
Universal vs Server Load Functions
Universal loads (+page.ts) run on the server for the first request and in the browser for navigations, calling APIs directly from the client during navigation. Server loads (+page.server.ts) always run on the server; during navigation, the client fetches their serialised output. Use server loads for database access, secrets and heavy processing; use universal loads when data comes from public APIs that the client can call directly (avoiding a server hop during navigation) or when returning non-serialisable values. Mixing them carefully can make navigations faster and servers lighter.
Invalidation and Rerunning Loads
SvelteKit reruns load functions when their dependencies change: URL params they read, fetch URLs (via depends), or parent data. Loads that read more than they need (the entire URL, all search params) rerun more often than necessary, adding requests on every navigation or query change. Read only the params you use, and use depends with custom keys plus invalidate to refresh data deliberately.
Diagnosing Load Function Performance
Add timing to load functions to see where time goes: wrap each request with performance.now() and report durations via Server-Timing headers (using setHeaders) for server loads, or log them during development for universal loads. In the browser, the Network panel shows __data.json requests for server loads during navigation, and their timing reveals slow endpoints. Look for loads that run more often than expected (because they depend on the whole URL), loads that refetch data already available from a parent, and endpoints that are not cached. Fixing the slowest endpoint usually improves both first loads and navigations at once.
Preloading on Mobile
Hover does not exist on touch devices, so preload-data="hover" falls back to touchstart, giving a shorter head start (roughly 100–200ms between touch and click). For mobile-heavy sites, combine data preloading on touch with code preloading on viewport so that route chunks are ready before the tap, and make loads fast enough that the remaining fetch fits within the time between touch and navigation. Prerendered routes help most here, since their data is a static file that can be served from the CDN cache in a few milliseconds.
Common Mistakes
- await parent() first. Blocks everything on layout data.
- Sequential awaits for independent data. Waterfalls.
- Eager code preloading on link-dense pages. Many unnecessary requests.
- Heavy logic in universal loads. Runs again in the browser on navigation.
Edge Cases
Authentication. Preloaded data uses the current session; personalised data should not be cached publicly.
Mutations. Use form actions or invalidate after mutations so stale preloaded data is not shown.
Long lists of links. Use data-sveltekit-preload-data="off" on low-value links to avoid unnecessary preloads.
External links. Preloading applies only to same-app routes.
FAQ
What does data-sveltekit-preload-data do?
It tells SvelteKit to run a route's load functions (and fetch code) when the user hovers or taps a link, before navigation.
Should I preload code on viewport?
It makes route chunks ready early. On pages with many links, it can create many requests; use it where links are few or important.
How do I avoid load waterfalls?
Run independent requests in parallel with Promise.all, avoid unnecessary await parent(), and stream non-critical data.
What is streaming in SvelteKit load?
Returning unresolved promises from a server load; SvelteKit sends the page and streams the promise results as they resolve.
When should I use +page.server.ts?
For data requiring secrets, database access or heavy processing, and when you want the server to cache results.
Does preloading increase server load?
Yes, for hovers that do not lead to clicks. Make loads cheap and cacheable, and use tap preloading on very busy pages.
Can I prerender SvelteKit routes?
Yes, with export const prerender = true, which produces static HTML and data at build time.
Do preloads use cached data on navigation?
Yes. If the user navigates shortly after a preload, SvelteKit uses the preloaded result instead of fetching again.
Can I disable preloading for one link?
Yes. Set data-sveltekit-preload-data="off" (or false in older versions) on the link or a parent element.
Do preloads respect cache headers?
Preloaded __data.json responses follow normal HTTP caching. Cacheable server loads make preloads cheap for both the server and repeat visits.
Related
- Speculative loading & prefetching — the browser-level equivalent.
- Time to First Byte (TTFB) optimization — first-load server time.
- Astro & SvelteKit performance — the topic overview.