| name | sveltekit-and-svelte5-runes |
| description | Complete architecture patterns, state management with Svelte 5 Runes ($state, $derived, $effect, $props), SvelteKit SSR/streaming pipelines, form actions, and production anti-patterns. |
SvelteKit & Svelte 5 Runes Architecture Guide
Core Architectural Principles
1. Svelte 5 Runes Paradigm
- Fine-Grained Signals: Svelte 5 replaces stores (
writable, readable) and compiler-magic $: declarations with deep reactive primitives (Runes).
- Primitives:
$state(initial): Deeply reactive state definition. Use $state.raw(initial) for non-reactive large structures (e.g., WebGL objects, large immutable arrays).
$derived(expression): Pure, memoized computational projections. Use $derived.by(() => { ... }) for complex multi-statement computations.
$effect(() => { ... }): Side-effect execution (DOM mutation, external sync). Must remain free of state mutations to prevent feedback loops.
$props(): Component property declaration with TS interface support and default assignments.
$bindable(): Explicit opt-in two-way data binding for props.
- Class-Based Reactive Stores: Encapsulate domain logic inside standard ES classes utilizing
$state and $derived properties, completely eliminating legacy store boilerplate.
2. SvelteKit Data Flow & SSR Isolation
- Universal vs Server Load:
- Use
+page.server.ts for database access, secret keys, or direct server APIs.
- Use
+page.ts for universal client/server fetching or rendering logic.
- Streaming Async Data: Return non-awaited promises in
+page.server.ts to stream slow data dependencies using SvelteKit's built-in streaming support.
- Form Actions & Progressive Enhancement: Always leverage SvelteKit Form Actions with
use:enhance to ensure forms work seamlessly with or without JavaScript enabled.
Production Code Examples
Example 1: Svelte 5 Class-Based Reactive State Store
Location: src/lib/stores/cart.svelte.ts
export interface CartItem {
id: string
name: string
price: number
quantity: number
}
export class ShoppingCartStore {
items = $state<CartItem[]>([])
discountCode = $state<string | null>(null)
discountPercent = $state<number>(0)
itemCount = $derived(this.items.reduce((total, item) => total + item.quantity, 0))
subtotal = $derived(
this.items.reduce((sum, item) => sum + item.price * item.quantity, 0)
)
tax = $derived.by(() => {
const taxableAmount = Math.max(0, this.subtotal * (1 - this.discountPercent))
return taxableAmount * 0.08
})
total = $derived(.(, . * ( - .)) + .)
() {
existing = ..( i. === newItem.)
(existing) {
existing. +=
} {
..({ ...newItem, : })
}
}
() {
. = ..( i. !== id)
}
() {
item = ..( i. === id)
(item) {
(quantity <= ) {
.(id)
} {
item. = quantity
}
}
}
() {
. = code
. = percent /
}
() {
. = []
. =
. =
}
}
cartStore = ()
Example 2: SvelteKit Streaming Load Function & Page Component
Location: src/routes/dashboard/+page.server.ts
import type { PageServerLoad } from './$types'
import { error } from '@sveltejs/kit'
async function fetchFastMetrics(userId: string) {
return { userStatus: 'active', notificationsCount: 4 }
}
async function fetchSlowAnalytics(userId: string) {
await new Promise((resolve) => setTimeout(resolve, 1500))
return {
monthlyRevenue: 14250.0,
activeSubscriptions: 342,
chartData: [10, 25, 45, 80, 120]
}
}
export const load: PageServerLoad = async ({ locals, parent }) => {
const session = await locals.getSession()
(!session?.) {
(, )
}
fastMetrics = (session..)
slowAnalyticsPromise = (session..)
{
: session.,
fastMetrics,
: {
: slowAnalyticsPromise
}
}
}
Location: src/routes/dashboard/+page.svelte
<script lang="ts">
import type { PageData } from './$types'
// Component props in Svelte 5 using $props rune
let { data }: { data: PageData } = $props()
// Destructure reactive references
let fastMetrics = $derived(data.fastMetrics)
</script>
<div class="dashboard">
<h1>Welcome, {data.user.name}</h1>
<!-- Instant SSR Content -->
<section class="summary-cards">
<div class="card">Status: {fastMetrics.userStatus}</div>
<div class="card">Unread Notifications: {fastMetrics.notificationsCount}</div>
</section>
<!-- Streamed Content with Promise Resolution -->
<section class="analytics-section">
<h2>Analytics Overview</h2>
{#await data.streamed.analytics}
<div class="loading-skeleton">
<p>Loading deep analytics metrics...</p>
</div>
{:then analytics}
<div class="analytics-content">
<p>Monthly Revenue: ${analytics.monthlyRevenue.toLocaleString()}</p>
<p>Active Subscriptions: {analytics.activeSubscriptions}</p>
</div>
{:catch err}
<div class="error-box">
<p>Failed to load analytics: {err.message}</p>
</div>
{/await}
</section>
</div>
Example 3: Progressive Form Action with Action Directive
Location: src/routes/contact/+page.server.ts
import { fail, type Actions } from '@sveltejs/kit'
export const actions: Actions = {
submitForm: async ({ request }) => {
const formData = await request.formData()
const email = formData.get('email')?.toString()
const message = formData.get('message')?.toString()
if (!email || !email.includes('@')) {
return fail(400, { email, message, error: 'Invalid email address' })
}
if (!message || message.length < 10) {
return fail(400, { email, message, error: 'Message must be at least 10 characters long' })
}
return { success: true, messageId: 'msg_98127391' }
}
}
Location: src/routes/contact/+page.svelte
<script lang="ts">
import { enhance } from '$app/forms'
import type { ActionData } from './$types'
let { form }: { form: ActionData } = $props()
let isSubmitting = $state(false)
</script>
<form
method="POST"
action="?/submitForm"
use:enhance={() => {
isSubmitting = true
return async ({ update }) => {
isSubmitting = false
await update()
}
}}
>
<label>
Email Address:
<input type="email" name="email" value={form?.email ?? ''} required />
</label>
<label>
Your Message:
<textarea name="message" required>{form?.message ?? ''}</textarea>
</label>
{#if form?.error}
<p class="error-msg">{form.error}</p>
{/if}
{#if form?.success}
<p class="success-msg">Message sent successfully! Ref: {form.messageId}</p>
{/if}
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Sending...' : 'Send Message'}
</button>
</form>
Anti-Patterns & Common Pitfalls
❌ Anti-Pattern 1: Mutating Derived State or Using $effect for Computed Values
<script lang="ts">
let count = $state(0)
let double = $state(0)
// BAD: Synchronizing derived state using $effect creates extra render ticks & bugs
$effect(() => {
double = count * 2
})
// GOOD: Use $derived primitive for computational projections
let doubleDerived = $derived(count * 2)
</script>
❌ Anti-Pattern 2: Top-Level Request State Leaks in SvelteKit SSR
export let currentUser: User | null = null
export const load = async ({ locals }) => {
currentUser = locals.user
}
export const load = async ({ locals }) => {
return { user: locals.user }
}
❌ Anti-Pattern 3: Mixing Legacy Svelte 4 Store Syntax with Runes Mode
<script lang="ts">
import { writable } from 'svelte/store'
// AVOID: Mixing old writable store with Svelte 5 runes creates unnecessary complexity
const countStore = writable(0)
// GOOD: Native Svelte 5 $state
let count = $state(0)
</script>