Back to blog

Svelte 5 Runes: The Reactivity Revolution Challenging React and Vue in 2025

Hello HaWkers, while React and Vue dominate discussions about JavaScript frameworks, Svelte 5 quietly arrived with an innovation that's making developers rethink everything they know about reactivity: Runes.

Have you ever imagined writing JavaScript components that are naturally reactive, without hooks, without Composition API, and without Virtual DOM? Svelte 5 made this a reality.

What Are Runes and Why They Matter

Runes are special symbols that start with $ and signal to the Svelte compiler how to handle reactivity. Unlike React hooks or Vue's Composition API, Runes are purely compile-time, which means zero runtime overhead.

Svelte 5 introduced a completely new approach to reactivity that eliminates many of the limitations and complexities of traditional frameworks. Instead of relying on a Virtual DOM or a complex runtime dependency system, Runes allow the Svelte compiler to generate extremely efficient JavaScript code.

Main advantages of Runes:

  • Superior performance: No Virtual DOM, no reconciliation
  • Cleaner code: Less boilerplate than React hooks
  • Granular reactivity: Only what changes gets updated
  • Native type-safety: Works perfectly with TypeScript
  • Smaller bundle size: Only necessary code is included

$state: Reactivity Simpler Than useState

$state is the most fundamental Rune and replaces let for reactive variables. Compare with React and see the difference:

// React - useState hook
import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  const [message, setMessage] = useState('Hello');

  function increment() {
    setCount(count + 1);
  }

  return (
    <div>
      <p>Count: {count}</p>
      <p>{message}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
}

// Svelte 5 - $state Rune
<script>
  let count = $state(0);
  let message = $state('Hello');

  function increment() {
    count++; // Just this! Naturally reactive
  }
</script>

<div>
  <p>Count: {count}</p>
  <p>{message}</p>
  <button onclick={increment}>Increment</button>
</div>

<!-- Cleaner code, less boilerplate, same functionality -->

The beauty of $state is that it looks like vanilla JavaScript, but is completely reactive. You don't need setters, don't need to remember to call special functions - just change the value and the DOM updates automatically.

Svelte Runes reactivity

$derived: Simplified Computed Values

$derived creates computed values that automatically update when their dependencies change. Much more elegant than React's useMemo:

// React - useMemo for computed values
import { useState, useMemo } from 'react';

function ShoppingCart() {
  const [items, setItems] = useState([
    { name: 'Laptop', price: 999, quantity: 1 },
    { name: 'Mouse', price: 29, quantity: 2 }
  ]);

  // useMemo to avoid unnecessary recalculations
  const total = useMemo(() => {
    return items.reduce((sum, item) => {
      return sum + (item.price * item.quantity);
    }, 0);
  }, [items]);

  const itemCount = useMemo(() => {
    return items.reduce((count, item) => count + item.quantity, 0);
  }, [items]);

  return (
    <div>
      <p>Total Items: {itemCount}</p>
      <p>Total Price: ${total}</p>
    </div>
  );
}

// Svelte 5 - $derived Rune
<script>
  let items = $state([
    { name: 'Laptop', price: 999, quantity: 1 },
    { name: 'Mouse', price: 29, quantity: 2 }
  ]);

  // Automatic derivations, without useMemo
  let total = $derived(
    items.reduce((sum, item) => sum + (item.price * item.quantity), 0)
  );

  let itemCount = $derived(
    items.reduce((count, item) => count + item.quantity, 0)
  );
</script>

<div>
  <p>Total Items: {itemCount}</p>
  <p>Total Price: ${total}</p>
</div>

<!-- The compiler optimizes automatically -->

$derived automatically tracks its dependencies and only recomputes when necessary, without you needing to specify a dependency array like in React.

$effect: Side Effects Without useEffect Complexity

$effect is Svelte's answer to useEffect, but much more intuitive:

// React - useEffect with its pitfalls
import { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    let cancelled = false;

    async function fetchUser() {
      setLoading(true);
      try {
        const response = await fetch(`/api/users/${userId}`);
        const data = await response.json();

        if (!cancelled) {
          setUser(data);
        }
      } finally {
        if (!cancelled) {
          setLoading(false);
        }
      }
    }

    fetchUser();

    // Cleanup function - CRITICAL!
    return () => {
      cancelled = true;
    };
  }, [userId]); // Dependency array - forget it and have bugs

  if (loading) return <div>Loading...</div>;
  return <div>{user?.name}</div>;
}

// Svelte 5 - $effect Rune
<script>
  let { userId } = $props();
  let user = $state(null);
  let loading = $state(true);

  $effect(() => {
    let cancelled = false;
    loading = true;

    fetch(`/api/users/${userId}`)
      .then(r => r.json())
      .then(data => {
        if (!cancelled) user = data;
      })
      .finally(() => {
        if (!cancelled) loading = false;
      });

    return () => {
      cancelled = true;
    };
  });
  // No dependency array! Automatic tracking
</script>

{#if loading}
  <div>Loading...</div>
{:else}
  <div>{user?.name}</div>
{/if}

$effect automatically tracks all reactive variables used inside it and re-runs when they change. No dependency arrays to forget, no subtle stale closure bugs.

$props: Typed and Reactive Props

$props replaces Svelte's old props system with a cleaner and type-safe approach:

// React with TypeScript
interface ButtonProps {
  label: string;
  onClick: () => void;
  variant?: 'primary' | 'secondary';
  disabled?: boolean;
}

function Button({ label, onClick, variant = 'primary', disabled = false }: ButtonProps) {
  return (
    <button
      onClick={onClick}
      disabled={disabled}
      className={`btn btn-${variant}`}
    >
      {label}
    </button>
  );
}

// Svelte 5 - $props Rune with TypeScript
<script lang="ts">
  interface Props {
    label: string;
    onClick: () => void;
    variant?: 'primary' | 'secondary';
    disabled?: boolean;
  }

  let {
    label,
    onClick,
    variant = 'primary',
    disabled = false
  }: Props = $props();
</script>

<button
  onclick={onClick}
  {disabled}
  class="btn btn-{variant}"
>
  {label}
</button>

<style>
  .btn {
    padding: 0.5rem 1rem;
    border-radius: 0.25rem;
  }

  .btn-primary {
    background: #007bff;
    color: white;
  }

  .btn-secondary {
    background: #6c757d;
    color: white;
  }
</style>

Performance: The Numbers Don't Lie

Recent benchmarks show Svelte 5 with Runes outperforming React and Vue in various scenarios:

Rendering Benchmark (1000 items)

Framework Initial Render Update 10% Update 100% Memory
Svelte 5 Runes 42ms 3.2ms 28ms 1.8 MB
React 18 68ms 8.5ms 52ms 3.2 MB
Vue 3 55ms 5.1ms 41ms 2.4 MB
Solid.js 38ms 2.8ms 25ms 1.6 MB

Bundle Size (Minified Production)

  • Svelte 5 app: ~3.5 KB base + components
  • React 18 app: ~42 KB base + components
  • Vue 3 app: ~33 KB base + components

The difference is dramatic: a Svelte 5 application can be over 10x smaller than the React equivalent.

Composition and Reusability: Runes Outside Components

One of Svelte 5's most powerful features is being able to use Runes in normal JavaScript functions, creating reusable logic:

// composables/useCounter.ts - Shared logic
export function useCounter(initialValue = 0) {
  let count = $state(initialValue);
  let doubled = $derived(count * 2);

  function increment() {
    count++;
  }

  function decrement() {
    count--;
  }

  function reset() {
    count = initialValue;
  }

  return {
    get count() { return count; },
    get doubled() { return doubled; },
    increment,
    decrement,
    reset
  };
}

// Component.svelte - Using shared logic
<script>
  import { useCounter } from './composables/useCounter';

  const counter = useCounter(10);
</script>

<div>
  <p>Count: {counter.count}</p>
  <p>Doubled: {counter.doubled}</p>
  <button onclick={counter.increment}>+</button>
  <button onclick={counter.decrement}>-</button>
  <button onclick={counter.reset}>Reset</button>
</div>

This is similar to Vue 3's Composition API, but with cleaner syntax and better performance.

Svelte 5 vs React: When to Choose Each

Use Svelte 5 when:

✅ Performance is critical (public apps, mobile)
✅ Bundle size matters (PWAs, static sites)
✅ You want cleaner code with less boilerplate
✅ Starting a new project
✅ Want superior developer experience
✅ Need smooth animations and transitions

Use React when:

✅ Need the largest ecosystem of libraries
✅ Your team already masters React
✅ Enterprise project with support requirements
✅ Integration with legacy React systems
✅ React Native for mobile
✅ Job market (more React positions)

Migration and Adoption in 2025

Svelte 5 maintains compatibility with Svelte 4, allowing gradual migration:

// Svelte 4 - Still works!
<script>
  let count = 0;
  $: doubled = count * 2;

  function increment() {
    count += 1;
  }
</script>

// Svelte 5 - New syntax (recommended)
<script>
  let count = $state(0);
  let doubled = $derived(count * 2);

  function increment() {
    count++;
  }
</script>

// Both work in Svelte 5!

Adoption statistics (2025):

  • 17% of new JavaScript projects choose Svelte
  • 156% growth in NPM downloads in 2024
  • 87% satisfaction among developers (State of JS 2024)
  • Used by: Apple, Spotify, The New York Times, Philips

Challenges and Limitations

Despite the advantages, Svelte 5 still faces challenges:

1. Smaller Ecosystem: Fewer third-party libraries compared to React/Vue

2. Job Market: Fewer positions than React or Vue (but growing)

3. Server Components: Still no equivalent to React Server Components

4. Tools: Less IDE and extension support than React

5. Learning Curve: Different paradigm can confuse devs used to React

The Future of Svelte and Reactive Interfaces

Svelte 5 represents a different vision of how JavaScript frameworks should work. Instead of mimicking JavaScript behavior at runtime, it compiles to optimized JavaScript.

Trends for 2025-2026:

  • Better Server-Side Rendering support
  • SvelteKit 2.0 with enterprise features
  • Greater edge computing integration
  • Library ecosystem expansion
  • Automated migration tools

If you're interested in exploring alternatives to traditional frameworks, I recommend reading: Deno 2.0 vs Node.js: The JavaScript Runtime Battle where we explore how choosing the right runtime can impact as much as choosing the right framework.

Let's go! 🦅

💻 Master JavaScript for Real

The knowledge you gained in this article is just the beginning. There are techniques, patterns, and practices that transform beginner developers into sought-after professionals.

Invest in Your Future

I've prepared complete material for you to master JavaScript:

Payment options:

  • $4.90 (single payment)

📖 View Complete Content

Comments (0)

This article has no comments yet 😢. Be the first! 🚀🦅

Add comments