Svelte 5 Migration Guide June 5, 2026
โข
Updated June 5, 2026
โข
5 min read
โข
948 words
svelte
svelte5
migration
upgrade
Overview Complete guide for migrating Svelte 4 applications to Svelte 5. Covers breaking changes, codemods, and incremental adoption strategies.
Breaking Changes Summary Area Svelte 4 Svelte 5 Reactivity Compiler-driven ($:) Runes ($state, $derived, $effect) Props export let$props()Slots <slot>Snippets ({#snippet}, {@render}) Events on:clickonclickContext setContext/getContextStill works, but $state modules preferred Dispatch createEventDispatcherCallback props
Automated Migration 1. Run the Official Codemod 1
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:
Reactive statements ($:) โ codemod guesses $derived vs $effectSlot usages โ complex slot patterns need manual snippet conversionEvent dispatching โ dispatch('event', data) โ callback propsComponent typing โ update TypeScript interfacesStep-by-Step Migration Step 1: Update Dependencies 1
2
npm install -D svelte@latest @sveltejs/vite-plugin-svelte@latest
npm install svelte@latest
Update package.json:
1
2
3
4
5
6
{
"devDependencies" : {
"svelte" : "^5.0.0" ,
"@sveltejs/vite-plugin-svelte" : "^4.0.0"
}
}
Step 2: Run Codemod 1
npx @sveltejs/migrate@latest svelte-5
Step 3: Fix Reactive Statements Before (Svelte 4):
1
2
3
4
5
< script >
let count = 0 ;
$ : doubled = count * 2 ;
$ : if ( count > 10 ) console . log ( 'big' );
</ script >
After (Svelte 5):
1
2
3
4
5
6
7
8
< 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:
1
2
3
4
5
< script >
export let title = 'Default' ;
export let count : number ;
export let onClick : () => void ;
</ script >
After:
1
2
3
4
5
6
7
< 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):
1
2
3
4
< Card >
< div slot = "header" > Title</ div >
< p slot = "body" > Content</ p >
</ Card >
Before (Card.svelte):
1
2
3
4
< div class = "card" >
< header >< slot name = "header" /></ header >
< main >< slot name = "body" /></ main >
</ div >
After (Parent):
1
2
3
4
5
6
7
8
9
10
11
12
< script >
import Card from './Card.svelte' ;
</ script >
< Card >
{ # snippet header ()}
< div > Title</ div >
{ /snippet }
{ # snippet body ()}
< p > Content</ p >
{ /snippet }
</ Card >
After (Card.svelte):
1
2
3
4
5
6
7
8
9
10
11
< 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:
1
2
< button on:click = { handleClick } > Click</button >
< CustomComponent on:customEvent = { handleCustom } / >
After:
1
2
< button onclick = { handleClick } > Click</button >
< CustomComponent oncustomEvent = { handleCustom } / >
Step 7: Replace Event Dispatching Before (Child):
1
2
3
4
5
6
7
8
< script >
import { createEventDispatcher } from 'svelte' ;
const dispatch = createEventDispatcher ();
function select ( item ) {
dispatch ( 'select' , item );
}
</ script >
After (Child):
1
2
3
4
5
6
7
< script >
let { onSelect } : { onSelect ?: ( item : Item ) => void } = $props ();
function select ( item ) {
onSelect ? .( item );
}
</ script >
Parent usage:
1
< Child onSelect = { handleSelect } / >
Step 8: Update Component TypeScript 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 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 Option A: Per-Component (Recommended) Run codemod on entire codebase Fix one component at a time Test each component before moving on Svelte 4 and 5 components can coexist during migration Option B: Per-Feature Branch Create feature branch Migrate entire feature Test thoroughly 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 1
2
3
4
5
6
7
8
9
10
11
<!-- 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 1
2
3
4
5
<!-- WRONG: derivation with side effect -->
let x = $derived.by(() => { fetch (...); return y ; } );
<!-- CORRECT: use $effect -->
$effect(() => { fetch (...); } );
3. Mutating Props Directly 1
2
3
4
5
6
7
8
9
10
11
<!-- 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 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- 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 1
2
3
4
5
6
7
8
# Run tests after each component migration
npm test
# Check for TypeScript errors
npx svelte-check --tsconfig tsconfig.json
# Build verification
npm run build
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