Svelte 5 Migration Guide

sveltesvelte5migrationupgrade

Svelte 5 Migration Guide

Overview

Complete guide for migrating Svelte 4 applications to Svelte 5. Covers breaking changes, codemods, and incremental adoption strategies.

Breaking Changes Summary

AreaSvelte 4Svelte 5
ReactivityCompiler-driven ($:)Runes ($state, $derived, $effect)
Propsexport let$props()
Slots<slot>Snippets ({#snippet}, {@render})
Eventson:clickonclick
ContextsetContext/getContextStill works, but $state modules preferred
DispatchcreateEventDispatcherCallback props

Automated Migration

1. Run the Official Codemod

npx @sveltejs/migrate@latest svelte-5

This handles:

  • export let โ†’ $props()
  • on:event โ†’ onevent
  • $: โ†’ $derived / $effect
  • <slot> โ†’ snippets (partial)

2. Manual Fixes Required After Codemod

The codemod is ~90% accurate. You must review:

  1. Reactive statements ($:) โ€” codemod guesses $derived vs $effect
  2. Slot usages โ€” complex slot patterns need manual snippet conversion
  3. Event dispatching โ€” dispatch('event', data) โ†’ callback props
  4. Component typing โ€” update TypeScript interfaces

Step-by-Step Migration

Step 1: Update Dependencies

npm install -D svelte@latest @sveltejs/vite-plugin-svelte@latest
npm install svelte@latest

Update package.json:

{
  "devDependencies": {
    "svelte": "^5.0.0",
    "@sveltejs/vite-plugin-svelte": "^4.0.0"
  }
}

Step 2: Run Codemod

npx @sveltejs/migrate@latest svelte-5

Step 3: Fix Reactive Statements

Before (Svelte 4):

<script>
  let count = 0;
  $: doubled = count * 2;
  $: if (count > 10) console.log('big');
</script>

After (Svelte 5):

<script>
  let count = $state(0);
  let doubled = $derived(count * 2);
  
  $effect(() => {
    if (count > 10) console.log('big');
  });
</script>

Decision tree for $: x = ...:

  • Pure computation โ†’ $derived
  • Side effect (DOM, fetch, console) โ†’ $effect
  • Complex logic needing function โ†’ $derived.by(() => ...)

Step 4: Convert Props

Before:

<script>
  export let title = 'Default';
  export let count: number;
  export let onClick: () => void;
</script>

After:

<script lang="ts">
  let { 
    title = 'Default', 
    count = 0, 
    onClick 
  } = $props<{ title?: string; count?: number; onClick?: () => void }>();
</script>

Step 5: Convert Slots to Snippets

Before (Parent):

<Card>
  <div slot="header">Title</div>
  <p slot="body">Content</p>
</Card>

Before (Card.svelte):

<div class="card">
  <header><slot name="header" /></header>
  <main><slot name="body" /></main>
</div>

After (Parent):

<script>
  import Card from './Card.svelte';
</script>

<Card>
  {#snippet header()}
    <div>Title</div>
  {/snippet}
  {#snippet body()}
    <p>Content</p>
  {/snippet}
</Card>

After (Card.svelte):

<script>
  let { header, body } = $props<{ 
    header: Snippet; 
    body: Snippet 
  }>();
</script>

<div class="card">
  <header>{@render header()}</header>
  <main>{@render body()}</main>
</div>

Step 6: Convert Event Handlers

Before:

<button on:click={handleClick}>Click</button>
<CustomComponent on:customEvent={handleCustom} />

After:

<button onclick={handleClick}>Click</button>
<CustomComponent oncustomEvent={handleCustom} />

Step 7: Replace Event Dispatching

Before (Child):

<script>
  import { createEventDispatcher } from 'svelte';
  const dispatch = createEventDispatcher();
  
  function select(item) {
    dispatch('select', item);
  }
</script>

After (Child):

<script>
  let { onSelect }: { onSelect?: (item: Item) => void } = $props();
  
  function select(item) {
    onSelect?.(item);
  }
</script>

Parent usage:

<Child onSelect={handleSelect} />

Step 8: Update Component TypeScript

<!-- Svelte 5 component with typed props -->
<script lang="ts">
  interface Props {
    title: string;
    count?: number;
    onAction?: (id: string) => void;
    renderItem?: Snippet<[Item]>;
  }
  
  let { 
    title, 
    count = 0, 
    onAction, 
    renderItem 
  } = $props<Props>();
</script>

Incremental Migration Strategy

  1. Run codemod on entire codebase
  2. Fix one component at a time
  3. Test each component before moving on
  4. Svelte 4 and 5 components can coexist during migration

Option B: Per-Feature Branch

  1. Create feature branch
  2. Migrate entire feature
  3. Test thoroughly
  4. Merge

Compatibility Notes

  • Svelte 4 components work inside Svelte 5 apps
  • Svelte 5 components work inside Svelte 4 apps (with caveats)
  • Shared state via $state in .svelte.js works across versions
  • Don’t mix runes and legacy reactivity in same component

Common Pitfalls

1. Forgetting $state for Mutable Values

<!-- WRONG: count won't be reactive -->
<script>
  let count = 0;
  function inc() { count++; }
</script>

<!-- CORRECT -->
<script>
  let count = $state(0);
  function inc() { count++; }
</script>

2. Using $derived for Side Effects

<!-- WRONG: derivation with side effect -->
let x = $derived.by(() => { fetch(...); return y; });

<!-- CORRECT: use $effect -->
$effect(() => { fetch(...); });

3. Mutating Props Directly

<!-- WRONG: props are read-only from parent perspective -->
<script>
  let { items } = $props();
  items.push(newItem); // Mutates parent's array!
</script>

<!-- CORRECT: use callback or return new array -->
<script>
  let { items, onAdd } = $props();
  function add() { onAdd?.(newItem); }
</script>

4. Slot Migration Edge Cases

<!-- Default slot + named slots -->
<Layout>
  <header slot="header">...</header>
  <main>Default content</main>
</Layout>

<!-- Becomes: -->
<Layout>
  {#snippet header()}...{/snippet}
  {#snippet default()}...{/snippet}
</Layout>

<!-- Layout.svelte: -->
<script>
  let { header, default: children } = $props();
</script>
<header>{@render header?.()}</header>
<main>{@render children?.()}</main>

Testing

# Run tests after each component migration
npm test

# Check for TypeScript errors
npx svelte-check --tsconfig tsconfig.json

# Build verification
npm run build

Performance Validation

After migration, verify:

  • Bundle size (should be similar or smaller)
  • Hydration time (improved with fine-grained reactivity)
  • Runtime performance (no more $: statement overhead)

Resources

Evolution Notes

Content last updated: 2026-06-05 Next review: 2026-06-12