Skip to content

Svelte 5 & SvelteKit Reference: Runes, Stores, Routing, Form Actions & Adapters

Svelte 5 compiles away the framework — no virtual DOM, no runtime diffing. The new Runes API ($state, $derived, $effect) replaces the old reactive let declarations and $: labels. SvelteKit is the full-stack layer: filesystem routing, SSR/SSG, server actions, and load functions. The mental model shift: Svelte is a compiler that turns your components into vanilla JS; SvelteKit is a framework built on Vite + that compiler.

1. Svelte 5 Runes — Reactivity

$state, $derived, $effect — the new reactive primitives (replaces let + $: in Svelte 4)
<!-- Counter.svelte (Svelte 5 runes) -->
<script>
  // $state: fine-grained reactive signal
  let count = $state(0);
  let name = $state("World");

  // $derived: computed from other state (replaces $: derived = ...)
  let doubled = $derived(count * 2);
  let greeting = $derived(`Hello, ${name}! Count: ${count}`);

  // $derived.by for multi-line:
  let stats = $derived.by(() => ({
    doubled: count * 2,
    isEven: count % 2 === 0,
    label: count > 10 ? "high" : "low",
  }));

  // $effect: side effects (replaces $: { ... } blocks)
  $effect(() => {
    document.title = greeting;   // runs when greeting changes
    return () => {
      document.title = "App";    // cleanup when component unmounts
    };
  });

  // $effect.pre: runs before DOM update (for measuring DOM)
  $effect.pre(() => {
    const height = element.offsetHeight;
    // runs before paint
  });
</script>

<button onclick={() => count++}>{count}</button>
<p>Doubled: {doubled}</p>
<input bind:value={name} />

<!-- Class props with $props() rune: -->
<script>
  // Props destructured from $props():
  let { title, count = 0, onchange } = $props();
  //     ^required  ^default  ^callback prop
</script>

2. Component Patterns — Events, Stores, Lifecycle

Custom events, writable stores, onMount/onDestroy, and component composition
<!-- Emitting events (Svelte 5 uses callback props, not createEventDispatcher) -->
<script>
  let { onselect } = $props();   // parent passes onselect={() => ...}
</script>

<button onclick={() => onselect({ id: 1, name: "Alice" })}>Select</button>

<!-- Svelte stores (still works in Svelte 5, good for shared state): -->
// stores/cart.ts:
import { writable, derived, get } from "svelte/store";

export const cartItems = writable([]);
export const cartTotal = derived(cartItems, ($items) =>
  $items.reduce((sum, item) => sum + item.price * item.qty, 0)
);
export function addToCart(item) {
  cartItems.update((items) => [...items, item]);
}

// In component — $ prefix auto-subscribes and unsubscribes:
<script>
  import { cartItems, cartTotal, addToCart } from "$lib/stores/cart";
</script>
<p>{$cartItems.length} items — £{$cartTotal.toFixed(2)}</p>
<button onclick={() => addToCart(product)}>Add to cart</button>

<!-- Lifecycle (same as Svelte 4): -->
<script>
  import { onMount, onDestroy, tick } from "svelte";

  onMount(() => {
    // DOM is ready — fetch data, init third-party libs
    const observer = new IntersectionObserver(callback);
    return () => observer.disconnect();  // cleanup = return function
  });

  // tick: wait for DOM update before reading:
  async function toggleAndMeasure() {
    open = true;
    await tick();    // DOM has updated now
    const height = panel.offsetHeight;
  }
</script>

3. SvelteKit Routing & Load Functions

Filesystem routing, +page.ts load, +layout.svelte, and route params
# SvelteKit file conventions:
# src/routes/
#   +page.svelte          → /
#   +layout.svelte        → wraps all child routes
#   +error.svelte         → error page
#   blog/
#     +page.svelte        → /blog
#     [slug]/
#       +page.svelte      → /blog/:slug
#       +page.ts          → load function for /blog/:slug
#   (auth)/               → route group (no URL segment)
#     login/+page.svelte  → /login
#     register/+page.svelte

// +page.ts — load function (runs on server AND client):
export async function load({ params, fetch, cookies, url }) {
  const res = await fetch(`/api/posts/${params.slug}`);
  if (!res.ok) {
    // SvelteKit error helper:
    import { error } from "@sveltejs/kit";
    error(404, "Post not found");
  }
  return { post: await res.json() };
}

// +page.server.ts — server-only load (DB access, secrets):
export async function load({ params, cookies }) {
  const sessionId = cookies.get("session");
  const user = await db.getUserBySession(sessionId);
  if (!user) redirect(303, "/login");
  const post = await db.getPost(params.slug);
  return { post, user };   // typed via PageServerLoad
}

// +page.svelte — receive data from load:
<script lang="ts">
  import type { PageData } from "./$types";
  let { data } = $props();   // Svelte 5; Svelte 4: export let data
  // data.post, data.user available
</script>

<h1>{data.post.title}</h1>

4. SvelteKit Form Actions & API Routes

+page.server.ts actions, use:enhance for progressive enhancement, and +server.ts API endpoints
// +page.server.ts — form actions (mutations without JavaScript requirement):
import { fail, redirect } from "@sveltejs/kit";
import type { Actions } from "./$types";

export const actions: Actions = {
  // default action (single action on page):
  default: async ({ request, cookies }) => {
    const data = await request.formData();
    const email = data.get("email") as string;
    const password = data.get("password") as string;

    if (!email || !password) {
      return fail(400, { email, error: "Email and password required" });
    }

    const user = await db.login(email, password);
    if (!user) return fail(401, { error: "Invalid credentials" });

    cookies.set("session", user.sessionId, { path: "/", httpOnly: true });
    redirect(303, "/dashboard");
  },

  // Named actions (multiple on same page):
  logout: async ({ cookies }) => {
    cookies.delete("session", { path: "/" });
    redirect(303, "/login");
  },
};

// +page.svelte — use:enhance for progressive enhancement:
<script>
  import { enhance } from "$app/forms";
</script>

<form method="POST" use:enhance>   <!-- works without JS, better with JS -->
  <input name="email" type="email" />
  <input name="password" type="password" />
  <button>Login</button>
</form>

// +server.ts — REST API endpoint (for non-form clients):
import { json } from "@sveltejs/kit";

export async function GET({ params, url }) {
  const posts = await db.getPosts({ page: Number(url.searchParams.get("page")) });
  return json(posts);
}

export async function POST({ request }) {
  const body = await request.json();
  const post = await db.createPost(body);
  return json(post, { status: 201 });
}

5. Deployment, SSR vs SSG, and Adapters

Choose the right adapter, prerender pages, and configure static vs dynamic rendering
// svelte.config.js — choose adapter:
import adapter from "@sveltejs/adapter-auto";         // Vercel/Netlify/Cloudflare auto-detect
// import adapter from "@sveltejs/adapter-node";      // self-hosted Node.js server
// import adapter from "@sveltejs/adapter-static";    // full static site (SSG)
// import adapter from "@sveltejs/adapter-cloudflare"; // Cloudflare Workers

export default {
  kit: {
    adapter: adapter(),
    prerender: { handleHttpError: "warn" },
  },
};

// Per-route rendering control (+page.ts or +layout.ts):
export const prerender = true;       // SSG this route at build time
export const ssr = false;            // CSR only (SPA mode for this route)
export const csr = false;            // no JS hydration (pure HTML)

// +layout.ts at root level for site-wide SSG:
export const prerender = true;       // prerender all routes by default

// Dynamic routes with SSG — provide all params:
export function entries() {
  return [
    { slug: "hello-world" },
    { slug: "getting-started" },
  ];
}
export const prerender = true;

// Environment variables:
// Private (server only): import { DATABASE_URL } from "$env/static/private";
// Public (client + server): import { PUBLIC_API_URL } from "$env/static/public";
// Runtime: import { env } from "$env/dynamic/private";

// Recommended project structure:
// src/lib/          → shared utilities ($lib alias)
// src/lib/server/   → server-only code (never sent to browser)
// src/routes/       → filesystem router
// static/           → static assets (no processing)

Track Svelte releases at ReleaseRun. Related: React Reference | Vue.js 3 Reference | Next.js App Router Reference | TypeScript Reference

🔍 Free tool: npm Package Health Checker — check SvelteKit and Svelte ecosystem packages for EOL status, known CVEs, and latest versions.

Founded

2023 in London, UK

Contact

hello@releaserun.com