Skip to content

Web Development · Frontend Frameworks

SolidJS in 2026: Fine-Grained Reactivity Without the Virtual DOM

SolidJS is not React with a different syntax. It compiles to real DOM operations, skips the virtual DOM entirely, and has a different mental model for reactivity. Here's what that means in practice.

Anurag Verma

Anurag Verma

8 min read

SolidJS in 2026: Fine-Grained Reactivity Without the Virtual DOM

Sponsored

Share

React’s dominance in frontend development is not because the virtual DOM is the best approach. It’s because React had a good API at the right time, built a massive ecosystem, and became the default answer to “how do we build UIs in JavaScript?” SolidJS makes a different set of trade-offs and gets measurably better results in several real metrics. Understanding why matters even if you never use Solid in production.

What Makes Solid Different

React’s model is: when state changes, re-render the component tree (or the parts that need to update), then diff the result against the previous virtual DOM, and apply the minimal set of DOM changes.

Solid’s model is: when state changes, run only the specific code that reads that state, and apply the DOM change directly. No component re-renders. No virtual DOM diffing. No re-executing the render function.

This difference is not cosmetic. It changes how you write components and what performance characteristics to expect.

Signals: The Core Primitive

Solid’s reactivity is built on signals. A signal is a getter/setter pair where the framework tracks which computations read the getter.

import { createSignal } from 'solid-js'

const [count, setCount] = createSignal(0)

// Read the signal
console.log(count()) // 0

// Update it
setCount(1)
console.log(count()) // 1

When count() is called inside a reactive context (like a component’s JSX or a createEffect), Solid registers that dependency. When setCount is called, Solid knows exactly which reactive computations depend on count and reruns only those.

Components Run Once

This is the mental model shift that trips up React developers. In React, components are functions that run on every render. In Solid, components are functions that run once (at mount time) and return JSX. Reactivity happens at the signal level, not the component level.

import { createSignal } from 'solid-js'

function Counter() {
  const [count, setCount] = createSignal(0)

  console.log('Counter function ran') // Only logs once

  return (
    <div>
      <p>Count: {count()}</p>
      <button onClick={() => setCount(c => c + 1)}>
        Increment
      </button>
    </div>
  )
}

Each time you click the button, count() inside the JSX updates, and only the text node inside <p> gets patched. The Counter function never runs again. No useMemo, no useCallback, no React.memo. There’s no re-render to optimize.

Derived State with createMemo

The equivalent of useMemo is createMemo. The key difference: createMemo creates a signal that other reactive computations can subscribe to. It runs once initially and then only when its dependencies change.

import { createSignal, createMemo } from 'solid-js'

function PriceCalculator() {
  const [quantity, setQuantity] = createSignal(1)
  const [unitPrice, setUnitPrice] = createSignal(49.99)

  const total = createMemo(() => quantity() * unitPrice())
  const tax = createMemo(() => total() * 0.08)
  const grandTotal = createMemo(() => total() + tax())

  return (
    <div>
      <input
        type="number"
        value={quantity()}
        onInput={e => setQuantity(Number(e.target.value))}
      />
      <p>Subtotal: ${total().toFixed(2)}</p>
      <p>Tax: ${tax().toFixed(2)}</p>
      <p>Total: ${grandTotal().toFixed(2)}</p>
    </div>
  )
}

When quantity changes, Solid recomputes total, then tax, then grandTotal in order, and updates only the three text nodes in the DOM. Only the computations that depend on quantity run. unitPrice and any computation depending only on it are untouched.

Side Effects with createEffect

createEffect is Solid’s equivalent of useEffect, but without the dependency array. You declare no dependencies. Solid tracks them automatically.

import { createSignal, createEffect } from 'solid-js'

const [userId, setUserId] = createSignal(null)
const [user, setUser] = createSignal(null)

createEffect(async () => {
  const id = userId()
  if (!id) return

  // Solid tracks that this effect reads userId()
  const response = await fetch(`/api/users/${id}`)
  setUser(await response.json())
})

When userId changes, the effect reruns. You never need to write [userId]. The tracking is automatic. This also means you can’t accidentally forget a dependency (a very common React bug).

Control Flow Components

One significant divergence from React: Solid uses control flow components (<Show>, <For>, <Switch>) instead of JavaScript expressions in JSX. This exists because Solid compiles JSX to direct DOM operations, and conditional rendering needs to be handled in a way the compiler can understand.

import { createSignal, Show, For } from 'solid-js'

function UserList() {
  const [users, setUsers] = createSignal([])
  const [loading, setLoading] = createSignal(true)

  return (
    <div>
      <Show
        when={!loading()}
        fallback={<p>Loading...</p>}
      >
        <Show
          when={users().length > 0}
          fallback={<p>No users found.</p>}
        >
          <ul>
            <For each={users()}>
              {(user) => (
                <li key={user.id}>{user.name}</li>
              )}
            </For>
          </ul>
        </Show>
      </Show>
    </div>
  )
}

<For> does keyed reconciliation without re-creating DOM nodes for items that haven’t changed. <Show> mounts and unmounts its children as the condition changes. The fallback prop handles the “else” case.

Coming from React, this feels verbose at first. After a week, the explicitness starts to feel like a feature. It’s clear what creates DOM nodes and what doesn’t.

Stores for Nested State

For complex nested state, Solid provides stores, which are reactive objects where individual properties can be tracked.

import { createStore } from 'solid-js/store'

const [state, setState] = createStore({
  user: {
    name: 'Ana Torres',
    preferences: {
      theme: 'dark',
      notifications: true,
    },
  },
  cart: [],
})

// Only updates the theme — doesn't affect anything
// that reads other parts of state
setState('user', 'preferences', 'theme', 'light')

// Append to cart
setState('cart', cart => [...cart, { id: 1, name: 'Widget', qty: 1 }])

Solid’s store uses proxies to track access at the property level. A component that reads state.user.name will only rerender when name changes, not when theme or cart changes. This granularity requires careful design in React to achieve with useMemo and context splitting.

SolidStart: The Meta-Framework

For full-stack Solid, SolidStart is the equivalent of Next.js. It handles SSR, file-based routing, and server functions.

// routes/api/users/[id].ts
import { APIEvent } from '@solidjs/start'
import { db } from '~/lib/db'

export async function GET({ params }: APIEvent) {
  const user = await db.user.findUnique({
    where: { id: params.id },
  })

  if (!user) {
    return new Response('Not found', { status: 404 })
  }

  return Response.json(user)
}

Server functions let you call server-side code directly from components, similar to Next.js Server Actions:

// server actions inline in components
import { action, useAction } from '@solidjs/router'

const updateUser = action(async (formData: FormData) => {
  'use server'
  const name = formData.get('name') as string
  await db.user.update({ where: { id: ... }, data: { name } })
})

function EditForm() {
  const update = useAction(updateUser)

  return (
    <form action={update} method="post">
      <input name="name" />
      <button type="submit">Save</button>
    </form>
  )
}

SolidStart is younger than Next.js and the ecosystem is smaller, but it’s stable enough for production use.

Performance Numbers

The js-framework-benchmark maintained by Stefan Krause measures DOM manipulation performance across frameworks. Solid consistently places near the top, ahead of React by meaningful margins on most operations.

In the mid-2025 results:

  • Create 1,000 rows: Solid is roughly 1.3x faster than React 18
  • Replace 1,000 rows: Solid is roughly 1.4x faster
  • Partial update (every 10th row): Solid is roughly 1.2x faster
  • Select row: Solid and React are roughly equivalent
  • Memory usage: Solid uses substantially less memory on large tables

These are microbenchmarks. Real apps have different bottlenecks. But the numbers are consistent enough that they reflect the architecture difference. Less overhead per update does compound across a large UI.

When to Consider Solid

Solid is a good fit when:

  • Performance is load-bearing: dashboards with frequent updates, real-time data visualization, games, anything where jank is noticeable
  • Bundle size matters: Solid’s core is smaller than React’s (no virtual DOM code) and tree-shakes well
  • You’re starting fresh: The ecosystem is smaller; retrofitting an existing React codebase is not practical
  • The team is experienced: Solid’s mental model requires unlearning React patterns. It’s not a beginner framework

React is still the right default for most teams. The ecosystem is larger by an order of magnitude. Component libraries, testing utilities, hiring pipelines, and documentation all favor React. Switching to Solid means accepting smaller community support in exchange for better runtime performance.

For a greenfield dashboard app or a performance-sensitive data visualization tool, the trade-off increasingly makes sense. For a marketing site or a CRUD application where performance is adequate, the ecosystem risk isn’t worth it.

Getting Started

npx degit solidjs/templates/ts my-solid-app
cd my-solid-app
npm install
npm run dev

Or with SolidStart:

npm create solid@latest my-app
# Choose SolidStart when prompted
cd my-app
npm install
npm run dev

Read through the Solid tutorial before writing any real code. It’s short (about 30 minutes) and the concept map it builds will save you hours of debugging subtle reactivity issues.

Sponsored

Sponsored

Discussion

Join the conversation.

Comments are powered by GitHub Discussions. Sign in with your GitHub account to leave a comment.

Sponsored