From harness-claude
Implements SvelteKit load functions for pre-render data fetching: server-only (+page.server.ts), universal (+page.ts), streaming, layouts, error handling, and invalidation.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Fetch route data before rendering using SvelteKit's load functions — server-only, universal, streaming, and invalidation patterns
Guides SvelteKit data flow: load functions in +page.server.ts vs +page.ts, form actions, server/client data handling, serialization, fail(), redirect(), error(), invalidateAll().
Enforces Svelte 5 best practices in SvelteKit: runes ($state, $derived, $effect), $props(), $bindable(), load functions, form actions, and SSR patterns to fix outdated Svelte 4 code.
Builds SvelteKit routes using file-system conventions with +page.svelte, +layout.svelte, route groups, dynamic segments like [id], [[optional]], and catch-alls. For adding routes and shared layouts.
Share bugs, ideas, or general feedback.
Fetch route data before rendering using SvelteKit's load functions — server-only, universal, streaming, and invalidation patterns
+page.ts (universal) and +page.server.ts (server-only)Server load (+page.server.ts):
+page.server.ts when you need secrets, database access, cookies, or server-only APIs. This function never runs in the browser:// src/routes/users/[id]/+page.server.ts
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ params, locals, cookies }) => {
const user = await locals.db.user.findUnique({
where: { id: params.id },
});
if (!user) throw error(404, 'User not found');
return { user };
};
locals (set in hooks.server.ts):export const load: PageServerLoad = async ({ locals }) => {
if (!locals.user) redirect(303, '/login');
return { user: locals.user };
};
Universal load (+page.ts):
+page.ts for load functions that can run on both server (initial request) and client (navigation). Use the provided fetch — it is enhanced by SvelteKit for deduplication and credentials:// src/routes/posts/+page.ts
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ fetch, params }) => {
const res = await fetch(`/api/posts?page=${params.page ?? 1}`);
const posts = await res.json();
return { posts };
};
Accessing load data in the page:
data as a prop (Svelte 5 with $props) or as export let data (Svelte 4):<!-- +page.svelte -->
<script lang="ts">
import type { PageData } from './$types'
let { data }: { data: PageData } = $props()
</script>
{#each data.posts as post}
<article>{post.title}</article>
{/each}
Error handling:
error or redirect from @sveltejs/kit to trigger the error page or redirect:import { error, redirect } from '@sveltejs/kit';
export const load: PageServerLoad = async ({ params }) => {
const post = await getPost(params.slug);
if (!post) throw error(404, { message: 'Post not found' });
if (post.status === 'draft') redirect(303, '/');
return { post };
};
Layout loads:
+layout.server.ts to provide data to all pages in a route segment. Layout data is merged with page data:// src/routes/(app)/+layout.server.ts
export const load: LayoutServerLoad = async ({ locals }) => {
return { currentUser: locals.user };
};
Streaming with promises:
{#await}:// +page.server.ts
export const load: PageServerLoad = async ({ params }) => {
// fast — included in initial HTML
const product = await getProduct(params.id);
// slow — streamed after initial render
const reviews = getReviews(params.id); // not awaited
return { product, reviews };
};
<!-- +page.svelte -->
{#await data.reviews}
<p>Loading reviews...</p>
{:then reviews}
{#each reviews as review}<Review {review} />{/each}
{/await}
Invalidation:
depends('app:tag') to declare a custom dependency; call invalidate('app:tag') to re-run the load function:// load function
export const load: PageLoad = async ({ fetch, depends }) => {
depends('app:cart');
const cart = await fetch('/api/cart').then((r) => r.json());
return { cart };
};
// After updating cart:
import { invalidate } from '$app/navigation';
await invalidate('app:cart');
invalidateAll() to re-run all load functions on the current page.Server vs. universal load — decision matrix:
| Need | Use |
|---|---|
| Database access | +page.server.ts |
| Cookies / session | +page.server.ts |
| Secret env vars | +page.server.ts |
| Works without JS | +page.server.ts |
| Runs on CDN edge | +page.ts |
| Client-side navigation data | +page.ts |
| Public API, no secrets | Either |
Data merging:
When a route has both +layout.server.ts and +page.server.ts, SvelteKit merges the returned data. If both return a key with the same name, the page-level value wins.
Type generation:
SvelteKit generates types for PageData, PageServerLoad, LayoutLoad, and LayoutServerLoad in .svelte-kit/types/. Always import from ./$types to get the correct inferred types.
The parent function:
Load functions can await parent layout data via parent():
export const load: PageLoad = async ({ parent }) => {
const { user } = await parent();
return { greeting: `Hello, ${user.name}` };
};
Fetch in load vs. $fetch:
Always use the fetch provided by SvelteKit's load function — it:
Do not use the global fetch directly inside load functions.
https://kit.svelte.dev/docs/load