SvelteKit made me rethink what a framework should feel like
April 2, 2026
Coming from React and Next.js, what surprised me about SvelteKit — the reactivity model, the routing conventions, and the one thing I still reach for React for.
The first surprise: no runtime overhead
The first thing that hits when you build a SvelteKit app is how little ceremony is involved. Svelte compiles components to vanilla JavaScript at build time. There's no virtual DOM, no reconciler, no runtime library shipped to the browser. The output is small because the framework doesn't travel with it.
This isn't just a performance story — it changes how you think about what the framework is doing. Svelte isn't a library you import; it's a compiler that transforms your component syntax into efficient DOM operations.
The reactivity model is the biggest shift from React
In React, state requires explicit declaration and the reconciler figures out what to re-render. In Svelte, any let variable declared in a component is reactive — updating it triggers a DOM update.
<script>
let count = 0
$: doubled = count * 2 // recalculates whenever count changes
</script>
<button on:click={() => count++}>
{count} clicked, doubled is {doubled}
</button>
The $: syntax declares a reactive statement — it re-runs when any reactive value it depends on changes. No dependency arrays, no stale closure bugs, no useCallback for event handlers. This takes a few hours to fully internalize and then becomes completely natural.
The tradeoff: the mutation model feels unusual if you're used to React's immutability conventions. Svelte expects you to reassign to trigger reactivity:
<script>
let items = []
function addItem(item) {
items = [...items, item] // reassignment triggers update
// items.push(item) // won't trigger — mutation doesn't
}
</script>
Routing by file convention
SvelteKit's filesystem routing is similar to Next.js App Router, with cleaner colocated server logic:
src/routes/
+layout.svelte # wraps all routes
+page.svelte # renders /
posts/
+page.svelte # renders /posts
+page.server.js # server-side data loading for /posts
[slug]/
+page.svelte # renders /posts/:slug
+page.server.js # per-post data loading
The server load function colocated with the route is the pattern I've found most intuitive:
// posts/[slug]/+page.server.js
export async function load({ params }) {
const post = await db.post.findUnique({
where: { slug: params.slug },
include: { author: true }
})
if (!post) throw error(404, 'Post not found')
return { post }
}
<!-- posts/[slug]/+page.svelte -->
<script>
export let data
</script>
<article>
<h1>{data.post.title}</h1>
<p>by {data.post.author.name}</p>
</article>
The data flows from server to client automatically. No getServerSideProps export, no prop drilling through layouts, no context.
Stores for cross-component state
Where React reaches for Context, Zustand, or Jotai, Svelte has a built-in store primitive:
// lib/stores.js
import { writable, derived } from 'svelte/store'
export const user = writable(null)
export const isLoggedIn = derived(user, $user => $user !== null)
<script>
import { user, isLoggedIn } from '$lib/stores'
</script>
{#if $isLoggedIn}
<span>Welcome, {$user.name}</span>
{/if}
The $ prefix auto-subscribes the component and handles cleanup on destroy. One way to do global state, documented in one place.
Forms: the model is different
SvelteKit's form actions are a clean abstraction for form handling without client-side JavaScript:
// +page.server.js
export const actions = {
default: async ({ request }) => {
const data = await request.formData()
const email = data.get('email')
await subscribe(email)
return { success: true }
}
}
<form method="POST">
<input name="email" type="email" required />
<button>Subscribe</button>
</form>
This works without any JavaScript on the client. Progressive enhancement with use:enhance adds optimistic updates and prevents full page reloads when JS is available.
What I still reach for React for
The ecosystem gap is real. React has years of library development in areas Svelte's ecosystem is still maturing: complex drag-and-drop, accessible date/time pickers, rich text editors, data grid components with virtual scrolling. The options exist in Svelte, but there are fewer of them and some are less battle-tested.
For teams already deep in React — component libraries, shared utilities, team expertise — the migration cost needs a concrete justification beyond "it's simpler." For greenfield projects where you're choosing from scratch, SvelteKit deserves serious consideration and often wins on developer experience for anything content-heavy or form-heavy.
References
Hi, I'm Martin Duchev. You can find more about my projects on my GitHub.