svelte-sveltekit-expert · v4.7.0 · 2026-11-05 · sha256 c24d6ad4227cfea9

svelte-sveltekit-expert v4.7.0B

Immutable. This exact content never changes and is served at /api/v1/blob/c24d6ad4227cfea9.

---
name: svelte-sveltekit-expert
description: "Svelte 5 Runes and SvelteKit 2 server-first application architecture / Arsitektur aplikasi Svelte 5 Runes dan SvelteKit 2 server-first."
author: "Roedy Rustam"
version: "4.7.0"

---

# Svelte 5 & SvelteKit 2 Expert (Runes Standard 2026)

[English](#english) | [Bahasa Indonesia](#bahasa-indonesia)

---

<a name="english"></a>
## English

### Orchestration & Integration
- **Upstream System Design**: `brainstorming`, `zero-to-prod-orchestrator` (Phase 3 & Phase 4).
- **Styling & Web Standards**: `modern-web-guidance`, `tailwind-expert`, `modern-css-native-expert`, `design-system-architect`.
- **Quality & Performance**: `performance-web-vitals`, `e2e-testing-expert`, `anti-slop`, `visual-ui-auditor`.
- **Backend & APIs**: `js-backend-expert`, `database-orm-expert`, `typescript-expert`.
- **Cross-Domain Synergy & Subagent Swarm Integration**: `senior-frontend`, `state-management-expert`, `tanstack-query-expert`, `pwa-offline-first-expert`

### Description
Production-grade guide for building high-performance web applications using **Svelte 5** and **SvelteKit 2+**. Covers the complete Runes reactivity paradigm (`$state`, `$derived`, `$derived.by`, `$effect`, `$props`, `$bindable`), snippet composition replacing legacy slots, progressive enhancement form actions (`use:enhance`), streaming server promises in `+page.server.ts`, and shallow routing (`pushState`) for modal overlays.

### Trigger Conditions
- Scaffolding new applications with Svelte 5 and SvelteKit 2.
- Refactoring legacy Svelte 3/4 reactive declarations (`let x = 0; $: doubled = x * 2;`) to Svelte 5 Runes.
- Composing modular UI components using Svelte 5 Snippets instead of legacy `<slot>`.
- Implementing form submissions with atomic server actions and zero-JS progressive enhancement.
- Streaming large asynchronous datasets to the client without blocking the initial SSR HTML payload.

---

### Core Architectural Patterns

#### 1. Complete Svelte 5 Runes Reference

| Rune | Purpose | Svelte 4 Legacy Syntax |
|---|---|---|
| `$state(val)` | Deeply reactive state proxy | `let val = ...` |
| `$state.raw(val)` | Shallow reactive state (ideal for immutable/large payloads) | N/A |
| `$derived(expr)` | Cached reactive derivation | `$: val = expr` |
| `$derived.by(() => {})` | Complex multi-line derived computation | `$: { ... }` |
| `$effect(() => {})` | Post-DOM mount & update side-effect with cleanup | `onMount`, `afterUpdate`, `$: { ... }` |
| `$effect.pre(() => {})` | Pre-DOM update effect (e.g. scroll position capture) | `beforeUpdate` |
| `$props()` | Type-safe destructured component props | `export let prop` |
| `$bindable()` | Explicit two-way prop binding allowance | Direct two-way binding |
| `$inspect(val)` | Automatic dev-mode deep state inspector | `console.log(val)` in `$:` |

#### 2. Component Composition with Svelte 5 Snippets (No Slots)
In Svelte 5, `<slot>` is deprecated. Components accept Snippets as typed functions:
```svelte
<!-- src/lib/components/DataTable.svelte -->
<script lang="ts" generics="T">
  import type { Snippet } from 'svelte';

  interface Props {
    items: T[];
    header: Snippet;
    row: Snippet<[T]>;
    empty?: Snippet;
  }

  let { items, header, row, empty }: Props = $props();
</script>

<div class="overflow-x-auto rounded-xl border border-border bg-card">
  <table class="w-full text-left text-sm">
    <thead class="border-b border-border bg-muted/50 text-xs uppercase font-semibold text-muted-foreground">
      {@render header()}
    </thead>
    <tbody class="divide-y divide-border">
      {#each items as item}
        <tr class="hover:bg-muted/30 transition-colors">
          {@render row(item)}
        </tr>
      {:else}
        {#if empty}
          <tr>
            <td colspan="100%" class="p-8 text-center text-muted-foreground">
              {@render empty()}
            </td>
          </tr>
        {/if}
      {/each}
    </tbody>
  </table>
</div>
```

```svelte
<!-- Usage in consumer page -->
<script lang="ts">
  import DataTable from '$lib/components/DataTable.svelte';

  interface User {
    id: string;
    name: string;
    email: string;
    role: string;
  }

  let users = $state<User[]>([
    { id: '1', name: 'Alice Smith', email: 'alice@example.com', role: 'Admin' },
    { id: '2', name: 'Bob Jones', email: 'bob@example.com', role: 'Developer' },
  ]);
</script>

{#snippet tableHeader()}
  <tr>
    <th class="p-3">Name</th>
    <th class="p-3">Email</th>
    <th class="p-3">Role</th>
  </tr>
{/snippet}

{#snippet tableRow(user: User)}
  <td class="p-3 font-medium">{user.name}</td>
  <td class="p-3 text-muted-foreground font-mono">{user.email}</td>
  <td class="p-3"><span class="px-2 py-0.5 rounded text-xs bg-primary/10 text-primary">{user.role}</span></td>
{/snippet}

<DataTable items={users} header={tableHeader} row={tableRow} />
```

#### 3. Two-Way Binding with `$bindable()`
```svelte
<!-- src/lib/components/SearchInput.svelte -->
<script lang="ts">
  interface Props {
    value: string;
    placeholder?: string;
  }

  let { value = $bindable(''), placeholder = 'Search...' }: Props = $props();
</script>

<input
  type="text"
  bind:value
  {placeholder}
  class="px-3 py-2 border rounded-md bg-background text-sm w-full focus:outline-none focus:ring-1 focus:ring-primary"
/>
```

#### 4. SvelteKit 2 Server Actions with Progressive Enhancement
Atomic server mutation with optimistic state and zero-JS fallback:
```typescript
// src/routes/tasks/+page.server.ts
import { fail } from '@sveltejs/kit';
import type { Actions, PageServerLoad } from './$types';
import { db } from '$lib/server/db';

export const load: PageServerLoad = async ({ locals }) => {
  // Non-blocking streaming promise: do not await slow telemetry data
  return {
    tasks: await db.tasks.findMany({ orderBy: { createdAt: 'desc' } }),
    streamedMetrics: fetchLiveMetrics(), // Streamed as Promise without blocking HTML
  };
};

export const actions: Actions = {
  createTask: async ({ request }) => {
    const data = await request.formData();
    const title = data.get('title')?.toString().trim();

    if (!title || title.length < 3) {
      return fail(400, { title, error: 'Title must contain at least 3 characters.' });
    }

    const newTask = await db.tasks.create({ data: { title, completed: false } });
    return { success: true, task: newTask };
  },
};

async function fetchLiveMetrics() {
  await new Promise((r) => setTimeout(r, 400));
  return { activeNodes: 12, uptime: '99.99%' };
}
```

```svelte
<!-- src/routes/tasks/+page.svelte -->
<script lang="ts">
  import { enhance } from '$app/forms';
  let { data, form } = $props();

  let isSubmitting = $state(false);
  let optimisticTasks = $state([...data.tasks]);

  $effect(() => {
    optimisticTasks = [...data.tasks];
  });
</script>

<div class="max-w-xl mx-auto py-8 space-y-6">
  <h1 class="text-2xl font-bold">Project Tasks</h1>

  <form
    method="POST"
    action="?/createTask"
    use:enhance={({ formData, cancel }) => {
      const title = formData.get('title')?.toString().trim();
      if (!title) {
        cancel();
        return;
      }

      isSubmitting = true;
      // Optimistic update
      optimisticTasks = [{ id: `temp-${Date.now()}`, title, completed: false }, ...optimisticTasks];

      return async ({ update }) => {
        isSubmitting = false;
        await update();
      };
    }}
    class="flex gap-2"
  >
    <input
      name="title"
      type="text"
      placeholder="What needs to be done?"
      required
      class="flex-1 px-3 py-2 border rounded-md bg-background text-sm"
    />
    <button
      type="submit"
      disabled={isSubmitting}
      class="px-4 py-2 bg-primary text-primary-foreground font-medium rounded-md disabled:opacity-50 text-sm"
    >
      {isSubmitting ? 'Saving...' : 'Add Task'}
    </button>
  </form>

  {#if form?.error}
    <p class="text-sm font-semibold text-destructive">{form.error}</p>
  {/if}

  <ul class="divide-y divide-border border rounded-xl bg-card">
    {#each optimisticTasks as task (task.id)}
      <li class="p-3 text-sm flex items-center justify-between">
        <span>{task.title}</span>
        {#if task.id.startsWith('temp-')}
          <span class="text-xs text-muted-foreground font-mono">Syncing...</span>
        {/if}
      </li>
    {/each}
  </ul>

  <!-- Await streamed non-blocking metrics -->
  {#await data.streamedMetrics}
    <div class="p-4 border rounded-lg bg-muted/40 text-xs animate-pulse text-muted-foreground">
      Loading cluster metrics...
    </div>
  {:then metrics}
    <div class="p-4 border rounded-lg bg-card text-xs flex justify-between font-mono">
      <span>Active Nodes: {metrics.activeNodes}</span>
      <span>Uptime: {metrics.uptime}</span>
    </div>
  {/await}
</div>
```

### Sovereign Execution, Token Efficiency & Invariants
| Dimension | Sovereign Mandate & Deterministic Contract |
| :--- | :--- |
| **Frontier Model Cascade** | Medium / High (`Gemini 4 Argon`, `Claude Sonnet 5.5 High`, `Muse Wonder High`, `Composer 2.5 Fast`) |
| **Availability Fallback** | `Gemini 4 Argon/Pro` -> `Gemini 3.8 Flash`; `Claude Opus 5.5` -> `Claude Opus 4.6` (Zero unhandled missing-model errors) |
| **KV-Cache & Token Protocol** | Pin static instructions & schemas at prompt prefix (`kv-cache-prefix-optimizer`); pass surgical `CONTEXT_MAP.md` slices (<250 tokens/call) during Swarm Fan-Out |
| **SaaT CLI & MCP Dispatch** | CLI: `vibes skill exec svelte-sveltekit-expert <audit\|eval\|verify\|generate\|blueprint>` / MCP: `vibes_skill_dispatch`, `vibes_visual_audit`, `vibes_ast_query`, `vibes_verify_gate` |
| **NFR & Anti-Slop Invariants** | LCP < 1.8s, INP < 100ms, CLS < 0.05, 1px crisp borders, 3-tier radius (6px/8px/12px), `tabular-nums`, 5-state interactive contract; 100% complete zero-placeholder output verified via `node scripts/check-anti-slop.js --strict` |

---


---

<a name="bahasa-indonesia"></a>
## Bahasa Indonesia

### Integrasi Orkestrasi
- **Ideasi & Desain Sistem**: `brainstorming`, `zero-to-prod-orchestrator` (Fase 3 & Fase 4).
- **Styling & Standar Web**: `modern-web-guidance`, `tailwind-expert`, `modern-css-native-expert`, `design-system-architect`.
- **Kualitas & Performa**: `performance-web-vitals`, `e2e-testing-expert`, `anti-slop`, `visual-ui-auditor`.
- **Backend & APIs**: `js-backend-expert`, `database-orm-expert`, `typescript-expert`.
- **Sinergi Lintas Domain & Integrasi Swarm Subagent**: `senior-frontend`, `state-management-expert`, `tanstack-query-expert`, `pwa-offline-first-expert`

### Deskripsi
Panduan produksi untuk membangun aplikasi web performa tinggi menggunakan **Svelte 5** dan **SvelteKit 2+**. Berfokus pada paradigma reaktivitas Runes secara menyeluruh (`$state`, `$derived`, `$derived.by`, `$effect`, `$props`, `$bindable`), komposisi Snippets pengganti slot lawas, progressive enhancement form actions (`use:enhance`), streaming promise server pada `+page.server.ts`, dan routing shallow (`pushState`) untuk modal URL-preserving.

### Kondisi Pemicu
- Membangun aplikasi baru menggunakan Svelte 5 dan SvelteKit 2.
- Merefaktor kode lama Svelte 3/4 (`let x = 0; $: doubled = x * 2;`) ke sistem Runes Svelte 5.
- Menggubah komponen UI modular menggunakan Snippets Svelte 5 alih-alih `<slot>` lawas.
- Mengimplementasikan pengiriman data formulir atomik via Server Actions dengan fallback zero-JS.
- Mengalirkan (*streaming*) data asinkron lambat ke klien tanpa memblokir rendering HTML SSR awal.

---

### Implementasi Pola Kunci

#### 1. Referensi Lengkap Runes Svelte 5

| Rune | Fungsi & Tujuan | Sintaks Lama Svelte 4 |
|---|---|---|
| `$state(nilai)` | State reaktif mendalam (*deep reactive proxy*) | `let nilai = ...` |
| `$state.raw(nilai)` | State reaktif dangkal (*shallow* untuk objek besar/imutable) | Tidak tersedia |
| `$derived(ekspresi)` | Nilai turunan ter-cache otomatis | `$: nilai = ekspresi` |
| `$derived.by(() => {})` | Kalkulasi turunan kompleks multi-baris | `$: { ... }` |
| `$effect(() => {})` | Efek samping pasca render DOM dengan siklus cleanup | `onMount`, `afterUpdate`, `$: { ... }` |
| `$effect.pre(() => {})` | Efek samping sebelum DOM di-update | `beforeUpdate` |
| `$props()` | Menerima props komponen secara terstruktur & type-safe | `export let prop` |
| `$bindable()` | Memberi izin binding dua arah eksplisit | Binding dua arah otomatis |
| `$inspect(nilai)` | Inspeksi state mendalam saat mode pengembangan | `console.log(nilai)` di `$:` |

#### 2. Komposisi Komponen dengan Svelte 5 Snippets (Tanpa Slot)
Pada Svelte 5, `<slot>` digantikan oleh Snippets bertipe (*typed snippets*):
```svelte
<!-- src/lib/components/TabelData.svelte -->
<script lang="ts" generics="T">
  import type { Snippet } from 'svelte';

  interface Props {
    data: T[];
    kepala: Snippet;
    baris: Snippet<[T]>;
    kosong?: Snippet;
  }

  let { data, kepala, baris, kosong }: Props = $props();
</script>

<div class="overflow-x-auto rounded-xl border border-slate-200 dark:border-slate-800 bg-white dark:bg-slate-900">
  <table class="w-full text-left text-sm">
    <thead class="border-b border-slate-200 dark:border-slate-800 bg-slate-50 dark:bg-slate-800/50 text-xs uppercase font-semibold text-slate-500">
      {@render kepala()}
    </thead>
    <tbody class="divide-y divide-slate-100 dark:divide-slate-800">
      {#each data as item}
        <tr class="hover:bg-slate-50 dark:hover:bg-slate-800/30 transition-colors">
          {@render baris(item)}
        </tr>
      {:else}
        {#if kosong}
          <tr>
            <td colspan="100%" class="p-8 text-center text-slate-400">
              {@render kosong()}
            </td>
          </tr>
        {/if}
      {/each}
    </tbody>
  </table>
</div>
```

#### 3. Form Action SvelteKit 2 dengan Progressive Enhancement & State Optimistik
```typescript
// src/routes/tugas/+page.server.ts
import { fail } from '@sveltejs/kit';
import type { Actions, PageServerLoad } from './$types';
import { db } from '$lib/server/db';

export const load: PageServerLoad = async () => {
  return {
    daftarTugas: await db.tasks.findMany({ orderBy: { createdAt: 'desc' } }),
    metrikServer: ambilMetrikServer(), // Streamed Promise tanpa menunda payload HTML awal
  };
};

export const actions: Actions = {
  tambahTugas: async ({ request }) => {
    const data = await request.formData();
    const judul = data.get('judul')?.toString().trim();

    if (!judul || judul.length < 3) {
      return fail(400, { judul, error: 'Judul tugas minimal terdiri dari 3 karakter.' });
    }

    const tugasBaru = await db.tasks.create({ data: { title: judul, completed: false } });
    return { success: true, tugas: tugasBaru };
  },
};

async function ambilMetrikServer() {
  await new Promise((r) => setTimeout(r, 400));
  return { nodeAktif: 12, uptime: '99.99%' };
}
```

```svelte
<!-- src/routes/tugas/+page.svelte -->
<script lang="ts">
  import { enhance } from '$app/forms';
  let { data, form } = $props();

  let sedangMenyimpan = $state(false);
  let daftarTugasOptimistik = $state([...data.daftarTugas]);

  $effect(() => {
    daftarTugasOptimistik = [...data.daftarTugas];
  });
</script>

<div class="max-w-xl mx-auto py-8 space-y-6">
  <h1 class="text-2xl font-bold">Daftar Tugas Proyek</h1>

  <form
    method="POST"
    action="?/tambahTugas"
    use:enhance={({ formData, cancel }) => {
      const judul = formData.get('judul')?.toString().trim();
      if (!judul) {
        cancel();
        return;
      }

      sedangMenyimpan = true;
      // Pembaruan UI Optimistik instan
      daftarTugasOptimistik = [{ id: `temp-${Date.now()}`, title: judul, completed: false }, ...daftarTugasOptimistik];

      return async ({ update }) => {
        sedangMenyimpan = false;
        await update();
      };
    }}
    class="flex gap-2"
  >
    <input
      name="judul"
      type="text"
      placeholder="Apa yang perlu dikerjakan?"
      required
      class="flex-1 px-3 py-2 border border-slate-300 dark:border-slate-700 rounded-md bg-transparent text-sm"
    />
    <button
      type="submit"
      disabled={sedangMenyimpan}
      class="px-4 py-2 bg-emerald-600 text-white font-medium rounded-md disabled:opacity-50 text-sm hover:bg-emerald-500 transition"
    >
      {sedangMenyimpan ? 'Menyimpan...' : 'Tambah Tugas'}
    </button>
  </form>

  {#if form?.error}
    <p class="text-sm font-semibold text-rose-600">{form.error}</p>
  {/if}

  <ul class="divide-y divide-slate-200 dark:divide-slate-800 border rounded-xl bg-white dark:bg-slate-900">
    {#each daftarTugasOptimistik as tugas (tugas.id)}
      <li class="p-3 text-sm flex items-center justify-between">
        <span>{tugas.title}</span>
        {#if tugas.id.startsWith('temp-')}
          <span class="text-xs text-slate-400 font-mono">Menyinkronkan...</span>
        {/if}
      </li>
    {/each}
  </ul>

  <!-- Menunggu data streaming metrik asinkron -->
  {#await data.metrikServer}
    <div class="p-4 border rounded-lg bg-slate-50 dark:bg-slate-800/40 text-xs animate-pulse text-slate-400">
      Memuat telemetri klaster server...
    </div>
  {:then metrik}
    <div class="p-4 border rounded-lg bg-white dark:bg-slate-900 text-xs flex justify-between font-mono">
      <span>Node Aktif: {metrik.nodeAktif}</span>
      <span>Tingkat Uptime: {metrik.uptime}</span>
    </div>
  {/await}
</div>
```

### Matriks Eksekusi Berdaulat, Efisiensi Token & Invarian
| Dimensi | Mandat Berdaulat & Kontrak Deterministik |
| :--- | :--- |
| **Kaskade Model Frontier** | Medium / High (`Gemini 4 Argon`, `Claude Sonnet 5.5 High`, `Muse Wonder High`, `Composer 2.5 Fast`) |
| **Invarian Fallback Ketersediaan** | `Gemini 4 Argon/Pro` -> `Gemini 3.8 Flash`; `Claude Opus 5.5` -> `Claude Opus 4.6` (Bebas gagal saat model belum tersedia/kuota penuh) |
| **Protokol Hemat Token & KV-Cache** | Kunci instruksi & skema statis di prefix awal (`kv-cache-prefix-optimizer`); kirim potongan `CONTEXT_MAP.md` (<250 token/panggilan) saat Fan-Out |
| **Eksekusi SaaT CLI & MCP** | CLI: `vibes skill exec svelte-sveltekit-expert <audit\|eval\|verify\|generate\|blueprint>` / MCP: `vibes_skill_dispatch`, `vibes_visual_audit`, `vibes_ast_query`, `vibes_verify_gate` |
| **Invarian NFR & Anti-Slop** | LCP < 1.8s, INP < 100ms, CLS < 0.05, border tegas 1px, radius 3-tier (6px/8px/12px), `tabular-nums`, kontrak interaktif 5-state; kode 100% tuntas tanpa placeholder diverifikasi melalui `node scripts/check-anti-slop.js --strict` |