Awesome-omni-skill sveltekit-latest
Quick-reference for SvelteKit + Svelte 5 development (Feb 2026)
install
source · Clone the upstream repo
git clone https://github.com/diegosouzapw/awesome-omni-skill
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/diegosouzapw/awesome-omni-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/development/sveltekit-latest" ~/.claude/skills/diegosouzapw-awesome-omni-skill-sveltekit-latest && rm -rf "$T"
manifest:
skills/development/sveltekit-latest/SKILL.mdsource content
SvelteKit + Svelte 5 Quick Reference (February 2026)
Current Versions
| Package | Version | Notes |
|---|---|---|
| Svelte | ~5.50.x | Svelte 5 is the current stable. Svelte 4 is legacy. |
| SvelteKit | ~2.50.x | SvelteKit 2 is current stable. SvelteKit 1 is legacy. |
| sv (CLI) | latest | Replaces . Use for all scaffolding. |
Scaffolding a New Project
# Current recommended command (npm create svelte@latest is DEPRECATED) npx sv create my-app cd my-app npm install npm run dev
sv create options
| Flag | Purpose |
|---|---|
| Project template |
| TypeScript config |
| Install add-ons (see below) |
| Skip interactive add-on prompt |
| Package manager |
Available add-ons
drizzle, eslint, better-auth, tailwindcss, prettier, vitest, playwright, storybook, mdsvex, paraglide, sveltekit-adapter, mcp, devtools-json
TypeScript Setup
TypeScript is built-in. Select "ts" when prompted by
npx sv create, or pass --types ts. No extra configuration needed.
- Use
in<script lang="ts">
files.svelte - Types are auto-generated on
ornpm run devnpx svelte-kit sync - Required tsconfig options (set automatically):
,verbatimModuleSyntax: true
,isolatedModules: truenoEmit: true
files for reactive modules (replaces.svelte.ts
when you need runes).ts
Svelte 5 Runes (Core Reactivity)
Runes replace Svelte 4's implicit reactivity (
let, $:) with explicit, portable primitives.
$state -- Reactive State
<script> let count = $state(0); // primitive let todos = $state([]); // deep reactive proxy (arrays/objects) </script> <button onclick={() => count++}>{count}</button>
- Deeply reactive: mutations to nested objects/arrays are tracked automatically
- Class fields:
inside class bodiesdone = $state(false);
$state.raw -- Non-reactive (reassign-only)
let items = $state.raw([1, 2, 3]); // items.push(4) -- NO EFFECT (not reactive) items = [...items, 4]; // works (reassignment)
Use for large collections you never mutate in place. Better performance.
$state.snapshot -- Static copy
let data = $state({ count: 0 }); console.log($state.snapshot(data)); // plain object, no proxy
Use when passing state to external libraries or
structuredClone.
$derived -- Computed Values
let count = $state(0); const doubled = $derived(count * 2);
$derived.by -- Complex derivations
const filtered = $derived.by(() => { return items.filter(item => item.active); });
$derived(expr) is equivalent to $derived.by(() => expr).
$effect -- Side Effects
$effect(() => { // Runs on mount and whenever dependencies change // Dependencies are auto-tracked console.log(`count is ${count}`); // Optional cleanup (returned function runs before re-execution) return () => { /* cleanup */ }; });
$effect.pre -- Before DOM update
$effect.pre(() => { // Runs before DOM is updated. Rare use case. });
Rule of thumb:
$derived for values, $effect for actions/side effects.
$props -- Component Props
<script> // Replaces "export let" let { name, count = 0, class: klass, ...rest } = $props(); </script>
- Destructure with defaults, renaming, and rest props
- Props are NOT bindable by default (see $bindable)
$bindable -- Two-way Binding
<script> let { value = $bindable('default') } = $props(); </script>
Parent can then use
bind:value={something}.
$inspect -- Debug
$inspect(count); // logs to console whenever count changes (dev only, stripped in prod)
Snippets (Replace Slots)
Slots are deprecated. Use snippets and
{@render} instead.
Default content (children)
<!-- Parent --> <Card> <p>This becomes the children snippet</p> </Card> <!-- Card.svelte --> <script> let { children } = $props(); </script> <div class="card"> {@render children?.()} </div>
Named snippets
<!-- Parent --> <Card> {#snippet header()} <h2>Title</h2> {/snippet} {#snippet footer()} <p>Footer</p> {/snippet} <p>Default children content</p> </Card> <!-- Card.svelte --> <script> let { header, footer, children } = $props(); </script> <div class="card"> {@render header?.()} {@render children?.()} {@render footer?.()} </div>
Snippets with parameters
<!-- Parent --> <List items={users}> {#snippet item(user)} <span>{user.name}</span> {/snippet} </List> <!-- List.svelte --> <script> let { items, item } = $props(); </script> {#each items as entry} {@render item(entry)} {/each}
Event Handling Changes
DOM events: remove the colon
<!-- Svelte 4 --> <button on:click={handler}>Click</button> <input on:input={handler} /> <!-- Svelte 5 --> <button onclick={handler}>Click</button> <input oninput={handler} />
Component events: callback props replace createEventDispatcher
<!-- Svelte 4 child --> <script> import { createEventDispatcher } from 'svelte'; const dispatch = createEventDispatcher(); </script> <button on:click={() => dispatch('submit', data)}>Submit</button> <!-- Svelte 5 child --> <script> let { onsubmit } = $props(); </script> <button onclick={() => onsubmit(data)}>Submit</button>
Event modifiers are gone
Replace
on:click|preventDefault with explicit calls:
<button onclick={(e) => { e.preventDefault(); handler(e); }}>Click</button>
Component Mounting (Programmatic)
// Svelte 4 const app = new App({ target: document.getElementById('app') }); // Svelte 5 import { mount, unmount } from 'svelte'; const app = mount(App, { target: document.getElementById('app') }); // later: unmount(app);
Cross-Module Reactive State (.svelte.ts files)
// counter.svelte.ts let count = $state(0); // Cannot export reassignable $state directly. Two patterns: // Pattern 1: Export object (mutate properties) export const counter = $state({ count: 0 }); export function increment() { counter.count++; } // Pattern 2: Export getter functions export function getCount() { return count; } export function increment() { count++; }
SSR caveat: Module-scoped
is a singleton shared across SSR requests. Always reset at render start. See "SSR Behavior for Module-Scoped State" below.$state
SSR Behavior for Module-Scoped State
What runs during SSR vs CSR
| Context | SSR | CSR |
|---|---|---|
Top-level code | YES | YES |
initialization | YES | YES |
/ | Compiled as IIFE (computed once at module init) | Reactive (recomputes on dependency change) |
/ | NO | YES |
| NO | YES |
| Template expressions | YES | YES |
The $derived SSR trap
$derived.by(() => ...) compiles to const x = (() => ...)() during SSR -- an immediately-invoked function that runs once at module initialization, not reactively. If module-scoped $state is set after the derived is initialized, the derived will NOT reflect the updated values.
Broken pattern:
// store.svelte.ts let page = $state(''); const snapshot = $derived.by(() => ({ page })); // Frozen at init during SSR! function setPage(p) { page = p; }
Working pattern:
// store.svelte.ts let page = $state(''); function buildSnapshot() { return { page }; } // Fresh read every call export const store = { get snapshot() { return buildSnapshot(); }, // Works in both SSR and CSR setPage(p) { page = p; }, };
During CSR, reading
$state variables inside a getter called from a template still triggers Svelte 5's fine-grained reactivity tracking.
SSR baseline pattern for layouts/pages
To populate state during SSR, call setters at the top level of
<script> blocks (not in $effect or onMount). Use $effect for client-side reactive updates:
<script> import { page } from '$app/stores'; import { myStore } from '$lib/stores/my.svelte'; let { data } = $props(); // SSR + CSR: runs on every render myStore.setPage(data.title); // CSR only: reactive updates on navigation $effect(() => { myStore.setPage(data.title); }); </script>
Avoiding state_referenced_locally warnings
When you need
$derived values for SSR baseline code, DON'T read the $derived at top level. Instead, compute the value directly from the source:
<!-- BAD: warns because $derived is read outside reactive context --> <script> let isAdmin = $derived(data.user?.role === 'admin'); if (isAdmin) { store.setAuth({...}); } // Warning! </script> <!-- GOOD: compute from source directly --> <script> let isAdmin = $derived(data.user?.role === 'admin'); // SSR baseline: use source data, not $derived if (data.user?.role === 'admin') { store.setAuth({...}); } // $effect for CSR reactivity uses $derived normally $effect(() => { if (isAdmin) { store.setAuth({...}); } }); </script>
Module-scoped $state and SSR safety
Module-scoped
$state is shared across all SSR requests (it's a singleton). SvelteKit SSR is synchronous per-request, so call reset() at the top of the root layout to prevent cross-request pollution:
<!-- +layout.svelte (root) --> <script> import { myStore } from '$lib/stores/my.svelte'; myStore.reset(); // Clear state before each render </script>
SvelteKit 2 Key Differences from SvelteKit 1
anderror()
no longer need to be thrown -- just call themredirect()
require explicitcookies.set/delete/serialize
parameterpath- Import
fromvitePreprocess
(not from SvelteKit)@sveltejs/vite-plugin-svelte
replaced byresolvePath
fromresolveRoute$app/paths- Paths are consistently relative or absolute based on
configpaths.relative - Requires Node 18.13+
Migration Commands
# Migrate Svelte 4 -> Svelte 5 npx sv migrate svelte-5 # Migrate SvelteKit 1 -> SvelteKit 2 npx sv migrate sveltekit-2
Project Structure (SvelteKit)
src/ lib/ # Shared code, importable as $lib/ routes/ # File-based routing +page.svelte # Page component +page.ts # Page load function (universal) +page.server.ts # Server-only load / form actions +layout.svelte # Layout component +layout.ts # Layout load function +layout.server.ts # Server-only layout load +error.svelte # Error page +server.ts # API endpoint (GET, POST, etc.) app.html # HTML template app.d.ts # Auto-generated types svelte.config.js # SvelteKit configuration vite.config.ts # Vite configuration
Common Patterns
Form Actions (server-side form handling)
<!-- +page.svelte --> <form method="POST" action="?/create"> <input name="title" /> <button>Create</button> </form>
// +page.server.ts import type { Actions } from './$types'; export const actions = { create: async ({ request }) => { const data = await request.formData(); const title = data.get('title'); // handle creation } } satisfies Actions;
Load Functions
// +page.ts (runs on server AND client) import type { PageLoad } from './$types'; export const load: PageLoad = async ({ fetch }) => { const res = await fetch('/api/data'); return { items: await res.json() }; };
// +page.server.ts (runs ONLY on server) import type { PageServerLoad } from './$types'; export const load: PageServerLoad = async ({ locals }) => { return { user: locals.user }; };