Web Development · Frontend Frameworks
SolidJS in 2026: Fine-Grained Reactivity Without the Virtual DOM
SolidJS skips the virtual DOM entirely, compiling to real DOM operations with fine-grained signal-based reactivity. Here's what that means in practice.
Anurag Verma
8 min read
Sponsored
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, the same primitive now spreading across Vue, Angular, and Svelte 5, and headed for standardization via TC39. 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, a discipline that shows up again in Svelte 5’s runes-based reactivity, which made a similar trade toward explicitness over magic.
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, one of the tradeoffs we walk through in more depth in React vs. Angular vs. Vue in 2026, and Vue’s own move toward Solid-style compilation in Vapor Mode shows this architecture isn’t unique to Solid anymore.
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.
Frequently asked questions
- Is SolidJS just React with different syntax?
- No. SolidJS's JSX looks similar to React's, but the reactivity model is fundamentally different: React re-renders a component function and diffs the result against a virtual DOM, while Solid runs each component once and updates only the specific DOM nodes tied to the signal that changed. There's no re-render to optimize with useMemo or useCallback.
- Do I need useMemo and useCallback in SolidJS?
- No. Since components only run once and reactivity happens at the signal level, there's no re-render for those hooks to guard against. createMemo exists for derived values that other reactive computations depend on, but it's used for computation reuse, not to prevent unnecessary re-renders like in React.
- Is SolidJS faster than React in production?
- On the js-framework-benchmark's DOM manipulation tests, Solid is roughly 1.2-1.4x faster than React 18 on row creation, replacement, and partial updates, with noticeably lower memory use on large tables. These are microbenchmarks; the gap that shows up in a real app depends on how update-heavy the UI actually is.
- Should I migrate an existing React app to SolidJS?
- Generally no. Solid's ecosystem, component libraries, and hiring pool are much smaller than React's, and retrofitting an existing React codebase isn't practical given the different mental model. Solid makes more sense for a new, performance-sensitive project, like a real-time dashboard, than for migrating something already built.
- What is SolidStart?
- SolidStart is Solid's meta-framework, comparable to Next.js for React. It handles server-side rendering, file-based routing, and server functions that can be called directly from components. It's younger and has a smaller ecosystem than Next.js, but the post considers it stable enough for production use.
Sponsored
More from this category
More from Web Development
Sponsored
Discussion
Join the conversation.
Comments are powered by GitHub Discussions. Sign in with your GitHub account to leave a comment.
Sponsored