Svelte und SvelteKit mit Claude Code entwickeln
Praxisleitfaden fuer Claude Code mit Svelte/SvelteKit: Setup, runes, Routing, form actions, Tests und sichere Aenderungen.
Warum Claude Code gut zu SvelteKit passt
Svelte ist ein UI-Framework, das deklarative Komponenten in schlankes JavaScript kompiliert. SvelteKit ergaenzt die Anwendungsebene: dateibasiertes Routing, Server Rendering, load-Funktionen, form actions, Endpoints und Deployment-Adapter. In einer echten App betrifft eine kleine Funktion oft mehrere Stellen: src/routes, src/lib/components, $lib/server, generierte Typen und Tests.
Claude Code ist ein agentisches Coding-Werkzeug, das ein Repository lesen, Dateien bearbeiten und Befehle ausfuehren kann. Gerade bei SvelteKit sollte die Aufgabe aber eng formuliert sein. Statt “baue eine komplette App” funktioniert besser: “Lies diese Route und diese Komponenten, erklaere den Datenfluss, schlage einen Plan vor und aendere danach nur diese Dateien.” So bleibt der Unterschied zwischen Servercode, Browsercode und UI-Komponenten pruefbar.
Fuer Einsteiger ist Svelte 5 der wichtigste Punkt. Runes sind die aktuelle reaktive Schreibweise: $props nimmt Komponenteneingaben entgegen, $state deklariert veraenderlichen Zustand, $derived berechnet Werte aus Zustand, und $effect ist fuer Browser-Nebeneffekte gedacht. Dieser Artikel zeigt Setup, Komponenten, gemeinsamen Zustand, Routing, Formulare, Tests, sichere Prompts und typische Stolperfallen anhand einer kleinen Aufgaben-App.
flowchart LR
A["Kleine Anforderung schreiben"] --> B["Claude Code liest relevante Dateien"]
B --> C["Svelte-Komponenten bearbeiten"]
C --> D["load/actions pruefen"]
D --> E["Checks und Tests ausfuehren"]
E --> F["git diff manuell reviewen"]
Projekt einrichten
Neue SvelteKit-Projekte starten aktuell mit der Svelte CLI: npx sv create my-app. Fuer eine reine Svelte-App auf Vite kann auch das Vite-Template svelte-ts sinnvoll sein. Der aktuelle Vite Guide nennt Node.js 20.19+ oder 22.12+ als Voraussetzung fuer Vite, daher sollte die Node-Version vor der Fehlersuche geklaert sein.
npx sv create claude-svelte-demo
cd claude-svelte-demo
npm install
npm run dev
claude
In einem unbekannten Repository lohnt sich zuerst plan mode. Claude Code liest dann Dateien und erstellt einen Plan, ohne Quellcode zu bearbeiten.
/plan
Ich moechte diesem SvelteKit-Projekt eine Aufgabenliste hinzufuegen.
Lies zuerst src/routes und src/lib, dann schlage die zu aendernden Dateien, den Datenfluss und den Testplan vor.
Noch keine Dateien bearbeiten.
claude --permission-mode plan
Projektregeln gehoeren in CLAUDE.md oder .claude/CLAUDE.md. Beispiele: bevorzugter Paketmanager, npm run check, Svelte 5 runes, Formulare ueber SvelteKit actions, keine Secrets ausserhalb von $lib/server, kein Commit ohne ausdrueckliche Bitte. Konkrete Regeln funktionieren besser als allgemeine Stilwuensche.
Kleine Komponenten mit Svelte 5 bauen
Die folgende Datei kann als src/lib/components/TaskCard.svelte eingefuegt werden. Sie nimmt eine Aufgabe und einen Callback entgegen, berechnet Anzeigetexte mit $derived und behaelt aria-pressed fuer Barrierefreiheit.
<!-- src/lib/components/TaskCard.svelte -->
<script lang="ts">
type Task = {
id: string;
title: string;
done: boolean;
estimateMinutes: number;
tags: string[];
};
let {
task,
onToggle
}: {
task: Task;
onToggle: (id: string) => void;
} = $props();
let statusLabel = $derived(task.done ? 'Erledigt' : 'Offen');
let estimateLabel = $derived(`${Math.ceil(task.estimateMinutes / 15) * 15} Minuten`);
</script>
<article class:done={task.done} class="task-card">
<div>
<p class="status">{statusLabel}</p>
<h3>{task.title}</h3>
<p>{estimateLabel}</p>
</div>
<ul aria-label="Tags">
{#each task.tags as tag}
<li>{tag}</li>
{/each}
</ul>
<button type="button" aria-pressed={task.done} onclick={() => onToggle(task.id)}>
{task.done ? 'Wieder oeffnen' : 'Als erledigt markieren'}
</button>
</article>
<style>
.task-card {
display: grid;
gap: 0.75rem;
border: 1px solid #ddd;
border-radius: 0.5rem;
padding: 1rem;
}
.done {
background: #f2fff5;
}
.status {
font-size: 0.875rem;
font-weight: 700;
}
ul {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
list-style: none;
padding: 0;
}
li {
border-radius: 999px;
background: #eef2ff;
padding: 0.2rem 0.6rem;
}
</style>
Ein guter Prompt schuetzt diese Schnittstelle:
Verbessere src/lib/components/TaskCard.svelte, aber behalte die Svelte 5 runes-Syntax bei.
Bedingungen:
- Task-Typ und onToggle-Signatur nicht aendern.
- onclick beibehalten, nicht zu altem on:click zurueckwechseln.
- aria-pressed am Button behalten.
- Nur Layout und leere Zustaende verbessern.
- Danach einen Komponententest vorschlagen.
So bleibt die Aenderung klein. Claude Code soll nicht aus einer UI-Verbesserung eine Migration ueber das ganze Projekt machen.
Gemeinsamer Zustand: erst runes, dann stores
Svelte 5 erlaubt runes in .svelte.ts-Dateien. Diese Module eignen sich fuer wiederverwendbare reaktive Logik und gemeinsamen Zustand. svelte/store bleibt nuetzlich fuer komplexe asynchrone Datenstroeme, manuelle Subscriptions oder Bibliotheken, die bereits Stores erwarten.
// src/lib/state/taskFilters.svelte.ts
export type TaskStatus = 'all' | 'open' | 'done';
export const taskFilters = $state({
query: '',
status: 'all' as TaskStatus,
tag: ''
});
export function resetTaskFilters() {
taskFilters.query = '';
taskFilters.status = 'all';
taskFilters.tag = '';
}
<!-- src/lib/components/TaskFilterPanel.svelte -->
<script lang="ts">
import { resetTaskFilters, taskFilters } from '$lib/state/taskFilters.svelte';
</script>
<section aria-label="Aufgabenfilter">
<label>
Suchwort
<input bind:value={taskFilters.query} placeholder="Rechnung, Artikel, Review..." />
</label>
<label>
Status
<select bind:value={taskFilters.status}>
<option value="all">Alle</option>
<option value="open">Offen</option>
<option value="done">Erledigt</option>
</select>
</label>
<button type="button" onclick={resetTaskFilters}>Zuruecksetzen</button>
</section>
Der haeufige Fehler ist SSR zu vergessen. window, document und localStorage existieren nicht auf dem Server. Fuer Browser-Persistenz sollte browser aus $app/environment verwendet werden.
// src/lib/state/theme.svelte.ts
import { browser } from '$app/environment';
export const themeState = $state({
theme: 'system' as 'system' | 'light' | 'dark'
});
export function loadTheme() {
if (!browser) return;
const saved = localStorage.getItem('theme');
if (saved === 'light' || saved === 'dark' || saved === 'system') {
themeState.theme = saved;
}
}
export function saveTheme(nextTheme: typeof themeState.theme) {
themeState.theme = nextTheme;
if (browser) localStorage.setItem('theme', nextTheme);
}
Routing, load und Servergrenzen
SvelteKit nutzt dateibasiertes Routing. src/routes/about wird zu /about, src/routes/tasks/[slug] zu einer dynamischen Route mit slug. Wenn Daten vor dem Rendern geladen werden, liegt daneben +page.server.ts. Datenbankzugriff, private API-Schluessel und privilegierte Logik bleiben in $lib/server.
// src/lib/server/tasks.ts
export type Task = {
id: string;
slug: string;
title: string;
done: boolean;
estimateMinutes: number;
tags: string[];
};
const tasks: Task[] = [
{
id: 'task-1',
slug: 'write-svelte-guide',
title: 'SvelteKit-Artikel entwerfen',
done: false,
estimateMinutes: 45,
tags: ['writing', 'svelte']
}
];
export async function getTaskBySlug(slug: string) {
return tasks.find((task) => task.slug === slug) ?? null;
}
// src/routes/tasks/[slug]/+page.server.ts
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
import { getTaskBySlug } from '$lib/server/tasks';
export const load: PageServerLoad = async ({ params }) => {
const task = await getTaskBySlug(params.slug);
if (!task) {
error(404, 'Task not found');
}
return { task };
};
<!-- src/routes/tasks/[slug]/+page.svelte -->
<script lang="ts">
import type { PageProps } from './$types';
let { data }: PageProps = $props();
</script>
<svelte:head>
<title>{data.task.title} | Tasks</title>
</svelte:head>
<article>
<p>{data.task.done ? 'Erledigt' : 'Offen'}</p>
<h1>{data.task.title}</h1>
<p>Schaetzung: {data.task.estimateMinutes} Minuten</p>
</article>
Prompt:
Lies src/routes/tasks/[slug] und src/lib/server/tasks.ts.
Fuege Task ein dueDate-Feld hinzu und zeige es auf der Detailseite an.
Server-Datenzugriff bleibt in $lib/server.
Benenne den [slug]-Route-Ordner nicht um.
Fuehre am Ende npm run check aus.
Form actions und progressive Verbesserung
SvelteKit form actions exportieren actions aus +page.server.ts. Ein normales <form method="POST"> funktioniert dadurch ohne JavaScript; use:enhance verbessert nur die Interaktion, wenn JavaScript verfuegbar ist.
// src/routes/contact/+page.server.ts
import { fail } from '@sveltejs/kit';
import type { Actions } from './$types';
export const actions = {
default: async ({ request }) => {
const formData = await request.formData();
const values = {
name: String(formData.get('name') ?? '').trim(),
email: String(formData.get('email') ?? '').trim(),
message: String(formData.get('message') ?? '').trim()
};
const errors: Record<string, string> = {};
if (values.name.length < 2) errors.name = 'Bitte mindestens 2 Zeichen eingeben.';
if (!values.email.includes('@')) errors.email = 'Bitte E-Mail-Adresse pruefen.';
if (values.message.length < 10) errors.message = 'Bitte mindestens 10 Zeichen eingeben.';
if (Object.keys(errors).length > 0) {
return fail(400, { values, errors });
}
console.log('New inquiry', values);
return { success: true };
}
} satisfies Actions;
<!-- src/routes/contact/+page.svelte -->
<script lang="ts">
import { enhance } from '$app/forms';
import type { PageProps } from './$types';
let { form }: PageProps = $props();
</script>
{#if form?.success}
<p role="status">Gesendet. Wir antworten innerhalb von 1 bis 3 Werktagen.</p>
{/if}
<form method="POST" use:enhance>
<label>
Name
<input name="name" value={form?.values?.name ?? ''} aria-invalid={!!form?.errors?.name} />
</label>
{#if form?.errors?.name}<p>{form.errors.name}</p>{/if}
<label>
E-Mail
<input name="email" type="email" value={form?.values?.email ?? ''} aria-invalid={!!form?.errors?.email} />
</label>
{#if form?.errors?.email}<p>{form.errors.email}</p>{/if}
<label>
Nachricht
<textarea name="message" rows="5" aria-invalid={!!form?.errors?.message}>{form?.values?.message ?? ''}</textarea>
</label>
{#if form?.errors?.message}<p>{form.errors.message}</p>{/if}
<button type="submit">Senden</button>
</form>
Keine Seiteneffekte ueber GET, keine Secrets im Browser und kein ungeprueftes HTML mit {@html}. Wenn Claude Code das Formular aendert, nennen Sie explizit: Servervalidierung behalten, ohne JavaScript nutzbar, Accessibility-Attribute nicht entfernen.
Tests, Einsatzfaelle und Fallen
Die Svelte-Testdokumentation beschreibt Vitest als naheliegende Wahl fuer Vite- und SvelteKit-Projekte. Component Tests mit Testing Library pruefen sichtbares Verhalten; Playwright eignet sich fuer komplette Flows.
// src/lib/components/TaskCard.test.ts
import { fireEvent, render, screen } from '@testing-library/svelte';
import { describe, expect, it } from 'vitest';
import TaskCard from './TaskCard.svelte';
describe('TaskCard', () => {
it('toggles the task when the button is clicked', async () => {
let toggledId = '';
render(TaskCard, {
task: {
id: 'task-1',
title: 'SvelteKit-Artikel schreiben',
done: false,
estimateMinutes: 45,
tags: ['writing']
},
onToggle: (id) => {
toggledId = id;
}
});
await fireEvent.click(screen.getByRole('button', { name: 'Als erledigt markieren' }));
expect(toggledId).toBe('task-1');
});
});
npm run check
npm run test
npm run build
git diff -- src/lib src/routes
| Einsatzfall | Gute Aufgabe fuer Claude Code | Menschliche Pruefung |
|---|---|---|
| Admin-Filter | Zustand mit $state, berechnete Ansichten mit $derived | URL-Filter und Berechtigungen |
| Blog oder CMS | [slug], load, SEO und 404 verbinden | HTML-Sanitizing, Drafts, Preview-Regeln |
| Lead-Formulare | Actions, Validierung, use:enhance, Tests | Datenschutz, Spam, Benachrichtigung |
| Migration Svelte 4 zu 5 | Einzelne Komponenten zu runes migrieren | Keine unkontrollierte Massenmigration |
Typische Fehler sind zu grosse Prompts, gemischte Svelte-4/5-Syntax, $lib/server-Importe in Komponenten, $effect fuer reine Berechnungen und zu schwache Tests. Aus Revenue-Sicht kann SvelteKit technische Inhalte direkt mit Formularen und Beratung verbinden; fuer Rollout-Hilfe gibt es die Claude Code consultation page. Vertiefend helfen der Getting-started guide, TypeScript tips und Testing strategies.
Offizielle Quellen und getestetes Ergebnis
Nutzen Sie Primaerquellen: Svelte docs, SvelteKit docs, SvelteKit form actions, Vite guide und Claude Code docs.
Lies die aktuelle SvelteKit-Struktur und aendere nur diesen Bereich.
Dateien: src/routes/contact/+page.svelte und src/routes/contact/+page.server.ts
Ziel: company-Feld zum Kontaktformular hinzufuegen.
Bedingungen:
- Svelte 5 runes beibehalten.
- use:enhance nicht entfernen.
- Servervalidierung ergaenzen.
- CTA-Text und Layout-Klassen nicht aendern.
- Vor Abschluss npm run check ausfuehren.
Zum Schluss 3 Zeilen: Aenderungen, Risiko, fehlende Tests.
Masa hat diesen Ablauf in einer kleinen SvelteKit-Aufgaben-App getestet. Plan mode vor der Bearbeitung reduzierte Nacharbeit am staerksten. Die Vorgaben “runes beibehalten”, “npm run check ausfuehren” und “nicht committen” fuehrten zu kleineren Diffs, die auch fuer Svelte-Einsteiger gut reviewbar waren.
Kostenloses PDF: Claude-Code-Cheatsheet
E-Mail eintragen und eine Seite mit Befehlen, Review-Gewohnheiten und sicheren Workflows herunterladen.
Wir schützen Ihre Daten und senden keinen Spam.
Über den Autor
Masa
Engineer für praktische Claude-Code-Workflows und Team-Einführung.
Ähnliche Artikel
Claude Code Workflow von Obsidian zu CLAUDE.md
Obsidian-Arbeitsnotizen in CLAUDE.md-Betriebsnotizen verwandeln und Kontext nicht ständig neu erklären.
Claude Code Revenue CTA Routing: Artikel zu PDF, Gumroad und Beratung führen
Ein Claude-Code-Ablauf, der Leser nach Absicht zu Gratis-PDF, Gumroad oder Beratung führt.
Claude-Code-Team-Handoff-Regeln: Belege, Berechtigungen, Rollback und Umsatzpfade
Ein praktisches Claude-Code-Handoff für Review-Belege, Berechtigungen, Rollback, Gratis-PDF, Gumroad und Beratung.