Svelte for React Developers: Everything That's Different and Why
A practical guide to Svelte for developers who already know React — what changes, what's simpler, what's harder, and whether the switch is worth it.
If you've been writing React for a while, you've internalized certain truths: state is immutable, effects need dependency arrays, the virtual DOM diffs and patches, hooks have rules, and everything is a function that returns JSX. Svelte looks at all of these "truths" and says: what if none of that was necessary?
Svelte is a compiler, not a runtime framework. It takes your component code and compiles it into efficient imperative JavaScript that directly manipulates the DOM. No virtual DOM. No diffing algorithm. No runtime library shipped to the browser. The result is smaller bundles, faster updates, and code that feels like you're just writing JavaScript, HTML, and CSS — because you mostly are.
The Fundamental Difference: Compiler vs Runtime
React ships a runtime (~45KB gzipped for React + ReactDOM) to every user's browser. This runtime maintains a virtual DOM tree, handles reconciliation, manages fiber scheduling, and coordinates updates. It's impressive engineering, but it's overhead.
Svelte ships no runtime. The compiler analyzes your components at build time and generates vanilla JavaScript that knows exactly how to update the DOM when state changes. A Svelte app that renders "Hello, World" ships essentially zero framework code.
This isn't just a bundle size optimization. It changes how you think about reactivity.
In React, you tell the framework "here's my new state" and the framework figures out what DOM changes are needed by comparing the old virtual DOM tree to the new one.
In Svelte, the compiler sees your state assignments and generates code that directly updates the specific DOM nodes that depend on that state. No comparison needed. No tree walking. Just element.textContent = newValue.
State: The Biggest Mindset Shift
In React, state is special. You declare it with useState, update it with setter functions, and if you forget the setter and mutate directly, nothing happens.
// React state
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
In Svelte 5 (the current version), state uses "runes" — special compiler hints that signal reactivity:
<!-- Svelte 5 state with runes -->
<script>
let count = $state(0);
</script>
<button onclick={() => count++}>
Count: {count}
</button>
Notice what's different. There's no setter function. You just assign to the variable. count++ works. count = count + 1 works. The compiler sees the $state rune and generates code to update the DOM whenever count is reassigned.
This extends to objects and arrays:
<script>
let todos = $state([
{ text: 'Learn Svelte', done: false },
{ text: 'Build something', done: false },
]);
function addTodo(text) {
// Direct mutation works — Svelte tracks it
todos.push({ text, done: false });
}
function toggleTodo(index) {
// This just works
todos[index].done = !todos[index].done;
}
</script>
In React, you'd need setTodos([...todos, newTodo]) or use Immer. In Svelte, you push to the array and the UI updates. The compiler generates proxy-based tracking that detects mutations.
Derived State and Effects
React's useMemo and useEffect have dependency arrays that you must keep correct. Get them wrong and you get stale closures or infinite loops.
// React derived state and effects
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
// Must specify [userId] or it won't re-run
useEffect(() => {
fetchUser(userId).then(setUser);
}, [userId]);
// Must specify [user] or it won't recompute
const displayName = useMemo(() => {
return user ? ${user.first} ${user.last} : 'Loading...';
}, [user]);
return <h1>{displayName}</h1>;
}
Svelte figures out dependencies automatically:
<script>
let { userId } = $props();
let user = $state(null);
// $effect automatically tracks which reactive values are read
$effect(() => {
fetchUser(userId).then(u => user = u);
});
// $derived automatically recomputes when dependencies change
let displayName = $derived(user ? ${user.first} ${user.last} : 'Loading...');
</script>
<h1>{displayName}</h1>
No dependency arrays. The compiler traces which reactive values are read inside $effect and $derived and re-runs them when those values change. This eliminates an entire category of bugs.
Component Structure
A React component is a JavaScript function that returns JSX. Styles are typically handled separately (CSS modules, styled-components, Tailwind).
A Svelte component is a .svelte file with three sections:
<!-- UserCard.svelte -->
<script>
let { name, email, role = 'user' } = $props();
let showEmail = $state(false);
</script>
<div class="card">
<h2>{name}</h2>
{#if showEmail}
<p class="email">{email}</p>
{/if}
<button onclick={() => showEmail = !showEmail}>
{showEmail ? 'Hide' : 'Show'} Email
</button>
<span class="badge" class:admin={role === 'admin'}>
{role}
</span>
</div>
<style>
.card {
padding: 1rem;
border: 1px solid #ddd;
border-radius: 8px;
}
.email {
color: #666;
}
.badge {
font-size: 0.75rem;
padding: 2px 8px;
border-radius: 4px;
background: #e2e8f0;
}
.badge.admin {
background: #fed7aa;
color: #c2410c;
}
</style>
The block is scoped to the component by default. Svelte adds unique class names at compile time so styles can't leak to other components. No CSS modules, no styled-components, no runtime CSS-in-JS overhead. Just write CSS and it's scoped automatically.
Control Flow
React uses JavaScript expressions inside JSX for conditionals and loops. This works but can get nested and hard to read.
// React control flow
return (
<div>
{isLoading ? (
<Spinner />
) : error ? (
<Error message={error} />
) : (
<ul>
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
)}
</div>
);
Svelte uses template syntax that reads more like HTML:
<div>
{#if isLoading}
<Spinner />
{:else if error}
<Error message={error} />
{:else}
<ul>
{#each items as item (item.id)}
<li>{item.name}</li>
{/each}
</ul>
{/if}
</div>
Neither approach is objectively better. The Svelte syntax is more readable for template-heavy components. The React approach is more flexible for complex logic. If you're used to Vue or Handlebars templates, Svelte's syntax will feel natural.
Animations and Transitions
This is where Svelte genuinely shines compared to React. Built-in transition directives handle enter/exit animations with zero additional libraries.
<script>
import { fade, slide, fly } from 'svelte/transition';
import { flip } from 'svelte/animate';
let items = $state([1, 2, 3, 4, 5]);
function remove(id) {
items = items.filter(i => i !== id);
}
function shuffle() {
items = items.sort(() => Math.random() - 0.5);
}
</script>
<button onclick={shuffle}>Shuffle</button>
{#each items as item (item)}
<div
animate:flip={{ duration: 300 }}
transition:slide={{ duration: 200 }}
onclick={() => remove(item)}
>
Item {item}
</div>
{/each}
Removing an item triggers a slide-out animation automatically. Reordering items triggers flip animations automatically. In React, you'd need Framer Motion or React Spring, each adding bundle size and API complexity.
SvelteKit: The Full Framework
Svelte is the component layer. SvelteKit is the full application framework — the equivalent of Next.js for React.
// src/routes/blog/[slug]/+page.server.js
export async function load({ params }) {
const post = await getPost(params.slug);
if (!post) {
throw error(404, 'Post not found');
}
return { post };
}
<!-- src/routes/blog/[slug]/+page.svelte -->
<script>
let { data } = $props();
</script>
<article>
<h1>{data.post.title}</h1>
{@html data.post.content}
</article>
SvelteKit provides file-based routing, server-side rendering, static site generation, API routes, and deployment adapters for Vercel, Cloudflare, Node, and more. If you know Next.js, the concepts map directly — layouts, loading functions, error boundaries, middleware.
Key differences from Next.js:
- Data loading uses separate
+page.server.jsfiles rather than async components orgetServerSideProps - Form handling uses progressive enhancement with
+page.server.jsactions - Layouts are
+layout.sveltefiles, notlayout.tsx - The routing convention is similar but uses
+prefixes for special files
What's Better in Svelte
Bundle size. A Svelte app ships less JavaScript because there's no runtime. For small-to-medium apps, the difference is significant. For large apps with many components, the gap narrows because each Svelte component includes its own generated update code. Reactivity model. No dependency arrays. No stale closures. No rules of hooks. State is just variables. This is genuinely simpler and eliminates real bugs. Scoped styles. Built-in, zero config, no runtime cost. Write CSS in your component, it's scoped. Animations. Built-in transitions and animations that handle enter/exit states gracefully. The API is elegant and covers 90% of animation needs. Learning curve. If you know HTML, CSS, and JavaScript, Svelte is approachable. There's less framework-specific knowledge to acquire compared to React's hooks mental model.What's Better in React
Ecosystem. React's library ecosystem is vastly larger. Authentication, data fetching, complex forms, charting, rich text editing — there's a mature React library for everything. Svelte's ecosystem is growing but smaller. Job market. More companies use React. More job postings require React. If career pragmatism matters, React is the safer bet. Community resources. More tutorials, courses, Stack Overflow answers, and blog posts. When you hit a problem in React, someone has documented the solution. Complex state management. For large applications with complex state graphs, React's ecosystem (Redux, Zustand, TanStack Query) is more mature. Svelte's state management story is improving but less battle-tested at scale. Server Components. React Server Components and the streaming architecture in Next.js are genuinely innovative. Svelte doesn't have an equivalent yet.Should You Switch?
If you're starting a new project and your team is open to it: Svelte is worth serious consideration, especially for small-to-medium applications, content sites, and projects where performance and simplicity are priorities.
If you have a large React codebase: don't rewrite it. The productivity loss isn't worth the theoretical gains. React is a fine tool and yours is already built.
If you're learning your first framework: Svelte is arguably a better starting point because it teaches you HTML, CSS, and JavaScript fundamentals rather than framework abstractions. But React's job market is larger.
The honest truth is that both React and Svelte produce excellent web applications. Svelte is simpler and ships less code. React has a bigger ecosystem and more career opportunities. The "best" choice depends on your specific context, and anyone who tells you otherwise is selling something.
Explore frontend development with different frameworks and approaches on CodeUp.