Skip to content

What Is SolidJS? The Framework Built on Fine-Grained Reactivity

SolidJS is a declarative JavaScript library that uses fine-grained reactivity to update the DOM directly, with no Virtual DOM and no reconciliation pass.

Tuan Tran Van
10 min read
Contents (9 sections)
  1. What is SolidJS?
  2. Why SolidJS doesn't need a virtual DOM
  3. Signals, effects and memos: the three reactive primitives
  4. Components run once: a different mental model from React
  5. Common traps when you start with SolidJS
  6. SolidStart: Solid as a full-stack framework
  7. Where Solid 1.x and the 2.0 release candidate stand
  8. When SolidJS is the right choice (and when it isn't)
  9. References

SolidJS is a declarative JavaScript library for building user interfaces that uses fine-grained reactivity to update the DOM directly.

It works without a Virtual DOM or a reconciliation pass, which sets it apart from most modern UI frameworks. Because it never diffs an entire component tree, a state change updates only the specific DOM nodes tied to that state.

That choice puts SolidJS closer to vanilla JavaScript than its competitors on most performance measures. Solid is a layer of reactive primitives with a deterministic execution model that maps your code directly to browser operations. You are trading a render cycle for a dependency graph.

SolidJS updates the real DOM directly with no Virtual DOM in between

What is SolidJS?

Ryan Carniato created SolidJS, and it has been in active development for nearly a decade. It uses JSX but compiles that syntax into direct DOM operations at build time rather than generating a virtual representation. The JSX will look familiar if you have written React, but the execution model underneath is completely different: it puts TypeScript first and keeps the runtime footprint small — roughly 7–7.6 KB.

Treat Solid as a fine-grained reactive layer rather than a monolithic framework. It handles the UI layer, while its companion SolidStart is the unopinionated orchestrator for full-stack work. The ecosystem is split between the stable 1.x line, which remains the standard for production, and the 2.0 release candidate (RC).

The current 1.x line has a proven API and mature documentation for stable deployments. Solid 2.0 is a coordinated release of the platform, including the core library, router, and meta-framework. During the RC phase, APIs can still change, and you have to keep the package versions inside that coordinated release compatible with each other.

The framework is designed to eliminate the render cycle entirely. Because components do not re-run during the application lifecycle, the library removes the non-deterministic behavior you get in frameworks that re-run functions repeatedly. Once setup is done, the reactive graph takes over and keeps the DOM in sync.

Why SolidJS doesn't need a virtual DOM

Solid and Virtual DOM (VDOM) frameworks like React differ mechanically in how they update. In a VDOM model, a state change re-runs component functions to generate a new virtual tree for reconciliation. Solid removes that middle layer by treating component functions as setup functions that run exactly once to build the reactive graph. It relies on a compiler that extracts static HTML into cloned template nodes, which are created once and then updated surgically.

The Virtual DOM update pipeline compared with the SolidJS update pipeline

The performance benefits show up in JS Framework Benchmark data. Solid's geometric mean overhead is 1.05x that of vanilla JavaScript, while React 19 shows a 1.42x overhead. The gap holds across individual operations: creating 1,000 rows costs 1.04x overhead in Solid versus 1.38x in React, and selecting a row is nearly native at 1.02x compared to React's 1.44x.

Memory allocation data tells the same story. Solid's memory overhead ratio is 1.26x relative to vanilla JS, whereas React 19 is more than double at 2.68x. In repeated create/clear cycles the gap widens, as React's memory usage can exceed 3x that of vanilla JS because the virtual tree structure persists.

Bundle size and first paint follow the same pattern. Solid ships with about 1.27x the overhead of vanilla JS for transferred size. React's runtime and VDOM requirements push its overhead to 12.76x. On low-end hardware or CPU-constrained mobile environments, that thinner runtime shows up directly as lower interaction latency and faster initial rendering.

Signals, effects and memos: the three reactive primitives

Solid's fine-grained reactivity rests on three core primitives: signals, effects, and memos. They are nodes in a reactive graph, and they propagate state changes synchronously.

The dependency graph between signals, memos and effects in SolidJS

  1. Signals are the primary data cells. They return a getter/setter tuple. The getter must be called as a function, count(), to create a subscription at runtime.
  2. Effects are automated responders that observe signals. They execute a function whenever their tracked dependencies update.
  3. Memos are cached derivations that store the result of a computation, recalculating only when their source signals change. They are an optimization boundary that prevents redundant work.
javascript
import { createSignal, createEffect } from "solid-js";
 
// Initialize signal
const [count, setCount] = createSignal(0);
 
// Create an automated responder (Effect)
createEffect(() => {
  // Accessing the getter count() here registers
  // this effect as a subscriber for runtime tracking.
  console.log("Current count:", count());
});
 
// Update the signal
setCount(5); // Synchronously triggers the effect

Solid uses a hybrid push/pull approach to stay glitch-free, which makes it impossible to observe an inconsistent state mid-update. Unlike React's useEffect, there are no manual dependency arrays. Solid manages subscriptions automatically by tracking which signal getters run inside the synchronous execution scope of an effect or memo.

Components run once: a different mental model from React

In Solid, components are factory functions, not render functions. They execute once to set up the reactive graph and DOM nodes, then effectively disappear from the execution stack. The JSX return statement tells Solid how to build the initial DOM and wire up the reactive bindings, but the function body is never called again.

A React component re-runs on every render while a SolidJS component runs once

That has one critical consequence — tracking is synchronous. Because Solid registers subscribers only during the synchronous execution of a scope, calling a signal inside a setTimeout or another asynchronous scope during setup does not register a subscription. You have to read every reactive dependency inside the immediate, synchronous flow of the component or effect.

Standard JavaScript control flow, such as if statements or Array.prototype.map, does not work reactively inside the JSX return statement, because that logic is only evaluated once during setup. If the underlying data changes, the UI will not update. Solid provides built-in control flow components that preserve fine-grained tracking:

  • <Show> handles conditional rendering.
  • <For> is optimized for rendering lists with stable identities (keyed by reference).
  • <Index> is optimized for lists where item values change but positions stay stable, minimizing DOM movement.
  • <Switch> and <Match> manage complex, multi-conditional logic.

These components keep surgical updates scoped to the specific nodes affected by state changes without re-triggering parent logic. They are the main mechanism for fast list and conditional rendering without a Virtual DOM.

Common traps when you start with SolidJS

The most common error for developers moving to Solid is props destructuring. In Solid, the props object is a reactive proxy. Reading a property like props.name inside a reactive scope creates a subscription. If you destructure const { name } = props; at the top of a component, you read the value once during setup and assign it to a local constant, permanently breaking the reactive link.

Reading props.name keeps reactivity while destructuring props severs the link

To manage component properties while keeping reactivity, use the utility functions Solid provides. mergeProps lets you define default values while keeping the object reactive, and splitProps lets you separate groups of props without destructuring the underlying proxy. Both keep the reactive graph intact across component boundaries.

Another frequent error is confusing a signal call with a signal reference. You must call a signal as a function — count() — to subscribe to its value. Referencing the signal without parentheses (count) passes the function itself. Passing a signal reference is a valid technique for composition or handing state to a child, but referencing it in a template without calling it produces no reactive updates at that DOM node.

Finally, the run-once nature applies to the entire component body. Developers often expect code inside the component, but outside of effects or JSX, to run again when props or state change. Because the component is only a setup function, you have to wrap any logic that needs to respond to changes explicitly in a createEffect, a createMemo, or a reactive JSX expression.

SolidStart: Solid as a full-stack framework

SolidStart is the meta-framework that extends Solid into a modular, full-stack orchestrator. It is built on Vite for bundling, Nitro for the server-agnostic web layer, and Vinxi as the underlying orchestrator. The architecture is deliberately modular and unopinionated, so you can swap individual components like the router when a project needs it.

The five pillars of SolidStart: Solid, Vite, Nitro, Vinxi and Seroval

Single-flight mutations are one of its bigger efficiency wins. Traditional frameworks often need two separate HTTP requests — one for the data mutation and a second for state revalidation — while SolidStart uses its router context to handle both in a single round trip. The "use server" directive makes this work, letting you define functions that execute only on the server but stay callable from client-side code.

The framework supports multiple rendering modes, including client-side rendering, server-side rendering, and static site generation. It uses streaming SSR to send HTML chunks to the browser as data resolves, which helps with slow APIs or edge environments. Streaming lets the browser start rendering the shell before the entire data payload is ready.

SolidStart also works well with headless CMS setups. Using the createAsync primitive, you can fetch data from a CMS API inside a server function. This supports route preloading, where data fetching runs in parallel with route rendering, so only the specific DOM nodes bound to that data update when the resource resolves.

Where Solid 1.x and the 2.0 release candidate stand

The SolidJS ecosystem currently maintains two paths: the stable 1.x line and the 2.0 release candidate. Solid 1.x is the recommended choice for production applications, since it offers a hardened API and thorough documentation.

The stable Solid 1.x line alongside the Solid 2.0 release candidate

The 2.0 release candidate is a coordinated effort to modernize the core library, the router, and the meta-framework at the same time. It focuses on refining asynchronous primitives and improving how the platform's modules fit together. One major goal is a standard async layer that behaves consistently across server and client runtimes.

Specifically, the ecosystem is moving toward createAsync as the primary data-fetching primitive, meant to replace createResource in route-related data loading. That fits the broader goal of 2.0: a more unified async model for full-stack applications. While the RC phase is active, expect API shifts as the core team finalizes the release.

Official migration guides will cover the move to 2.0 for anyone coming from Solid 1.x or earlier SolidStart releases. For now, keep 1.x for mission-critical production systems and use the 2.0 RC for greenfield exploration, or for projects where early access to the unified async primitives matters more than API stability.

When SolidJS is the right choice (and when it isn't)

SolidJS is a strong candidate for performance-critical UIs, data-intensive dashboards, and collaborative tools where interaction latency is the metric that matters. Its low-overhead runtime suits low-end hardware and bundle-sensitive environments where the memory and CPU costs of a Virtual DOM are unacceptable.

When to choose SolidJS and when to reconsider

React remains the pragmatic choice when a project needs the large ecosystem of third-party component libraries — specialized data grids, complex date pickers — that have not been ported to Solid. And if hiring breadth and onboarding a large team quickly are your main risks, the ubiquity of React may outweigh the mechanical performance gains of Solid's fine-grained reactivity.

References

Share this article