Aller au contenu principal

Rules: svelte

Svelte 5+ uses runes: $state, $derived, $effect, $props. Do not use the $: reactive syntax or implicit reactive let anymore.

Affected files

These rules apply to files matching the following patterns:

  • **/*.svelte
  • **/*.svelte.ts
  • **/*.svelte.js
  • **/svelte.config.*

Detailed rules

Svelte 5 / SvelteKit Rules

Runes (Svelte 5)

Svelte 5+ uses runes: $state, $derived, $effect, $props. Do not use the $: reactive syntax or implicit reactive let anymore.

<script lang="ts">
let count = $state(0)
let double = $derived(count * 2)

$effect(() => {
console.log('count changed:', count)
})

let { title, onIncrement }: { title: string; onIncrement?: () => void } = $props()
</script>

Migration Svelte 4 → 5

Svelte 4Svelte 5
let count = 0 (reactive)let count = $state(0)
$: double = count * 2let double = $derived(count * 2)
export let titlelet { title } = $props()
$: { console.log(count) }$effect(() => { console.log(count) })
<slot />{@render children?.()}

Props and events

  • $props() with typed destructuring
  • Events via callback props (not createEventDispatcher)
<script lang="ts">
let { onSubmit }: { onSubmit: (data: FormData) => void } = $props()
</script>

<button onclick={() => onSubmit(data)}>Submit</button>

Stores (Svelte 5)

Use rune classes rather than writable() when possible:

// lib/cart.svelte.ts
class Cart {
items = $state<CartItem[]>([])
total = $derived(this.items.reduce((sum, i) => sum + i.price, 0))

add(item: CartItem) {
this.items.push(item)
}
}

export const cart = new Cart()

For compatibility: writable() / readable() remain valid but prefer runes.

SvelteKit

FeatureUsage
+page.sveltePage component
+page.server.tsServer-only load function (DB, secrets)
+page.tsUniversal load function (client + server)
+layout.svelteParent layout
+server.tsAPI route / endpoint
hooks.server.tsServer middleware (auth, CORS)
form actionsProgressive enhancement mutations

Load functions

// +page.server.ts
export async function load({ params, cookies }) {
const user = await getUserFromSession(cookies.get('session'))
return { user }
}

Form actions

// +page.server.ts
export const actions = {
default: async ({ request }) => {
const data = await request.formData()
const title = data.get('title')
await db.posts.create({ title })
return { success: true }
},
}
<!-- +page.svelte -->
<form method="POST" use:enhance>
<input name="title" />
<button>Create</button>
</form>

Anti-patterns

AvoidPrefer
let count = 0 reactive (Svelte 5)let count = $state(0)
$: double = count * 2 (Svelte 5)$derived()
createEventDispatcherCallback props
<slot /> (Svelte 5){@render children?.()}
Sensitive data in +page.tsUse +page.server.ts
fetch directly in the componentload function with SvelteKit's fetch

Performance

  • SSR by default in SvelteKit
  • export const prerender = true for static pages
  • export const ssr = false for CSR-only (private dashboards)
  • AOT compilation → tiny bundles, no virtual DOM

Rules

IMPORTANT: Svelte 5: use runes ($state, $derived, $effect, $props). IMPORTANT: Sensitive data (secrets, DB queries): only in +page.server.ts. YOU MUST type props via $props() with TypeScript. NEVER use createEventDispatcher (legacy, use callback props). NEVER expose DATABASE_URL or secrets in +page.ts (universal = client leak).

Automatic application

These rules are automatically applied by Claude during:

  • Reading the matching files
  • Modifying code
  • Suggestions and fixes

See also