Why SolidJS Skips the Virtual DOM for Direct DOM Updates
SolidJS replaces Virtual DOM diffing with fine-grained reactivity. Components run once; signals subscribe DOM nodes directly. This blog covers the run-once model, the destructuring trap, control-flow primitives, and async patterns with concrete verification steps.
25 Feb 2026, 18:04 UTC

In most modern frontend frameworks, the standard approach to UI updates is the Virtual DOM (VDOM). When state changes, the framework creates a new tree of objects, compares it to the previous version (diffing), and applies the differences to the real DOM. While effective, this process carries a constant overhead: the framework must manage and traverse these trees in memory.
SolidJS takes a fundamentally different path. It eliminates the Virtual DOM entirely. Instead of re-rendering components, it uses fine-grained reactivity to link state directly to the specific DOM nodes. This means when a value changes, only the exact piece of text or attribute updates, leaving the rest of the component logic untouched.
The Run-Once Architecture
The most jarring shift for developers moving from React is understanding when code actually executes. In a VDOM-based framework, your component function runs every time the state changes. In Solid, the component function executes exactly once.
When the function runs initially, it sets up the reactive graph. It creates signals (state) and "subscribes" DOM elements to those signals. Once the initial render is complete, the function never runs again. The "magic" happens in the reactive observers that live outside the component execution lifecycle.
To verify this, place a console log in your component body:
import { createSignal } from "solid-js";
function Counter() {
const [count, setCount] = createSignal(0);
console.log("Component rendered"); // This only logs ONCE
return (
setCount(count() + 1)}>
Count is: {count()}
);
}
If you click the button, you will see the number increase in the UI, but the console will not fire again. This confirms that the framework isn't re-running your component; it is only updating the text node bound to count().
The Destructuring Trap
Because Solid relies on tracking property access to build its reactive graph, a common pitfall exists: destructuring props. In most frameworks, destructuring is considered best practice. In Solid, it breaks reactivity.
When you destructure a prop, you are extracting the value (or a reference) at the moment the component initializes. Since Solid needs to track the access to the props object at runtime to know when to update, pulling the value out severs that connection.
- Wrong:
const { name } = props; return <div>{name}...;(Will never update if name changes) - Right:
return <div>props.name</div>(Tracks access toname)
Efficient Lists with Control Flow
Since components don't re-run, you cannot use standard JavaScript methods like map() for lists. If you use map, it will only run once during initialization and will not reflect new data additions. Solid provides specialized primitives like <For> and <Show> to handle this.
The <For> component is specifically optimized for reconciliation. If a list reorders, Solid moves the existing DOM nodes rather than destroying and recreating them, which is significantly better for performance in complex lists.
import { For } from "solid-js";
function UserList(props) {
return (
{(user) => (
- {user.name}
)}
);
}
Trade-offs and Mental Models
The performance gains of fine-grained reactivity are significant—lower memory usage and faster update cycles—but the cost is a different mental model. You must stop thinking in "re-renders" and start thinking in "reactive subscriptions."
This shift affects how you structure async logic. In React, you might fetch data in a useEffect that re-runs on dependency changes. In Solid, createEffect runs once and tracks signals accessed inside it. If you need to re-fetch when a signal changes, you must access that signal inside the effect body.
import { createSignal, createEffect } from "solid-js";
function SearchResults() {
const [query, setQuery] = createSignal("");
createEffect(() => {
// Accessing query() here subscribes to changes
fetch(`/api/search?q=${query()}`).then(...);
});
return setQuery(e.target.value)} />;
}
Verification Checklist
Before committing to this architecture in production, verify these behaviors in your environment:
- Add
console.logto a component body and confirm it fires once despite multiple state updates. - Use
<For>with a reorderable list and inspect DOM nodes in DevTools to confirm nodes move rather than recreate. - Destructure a prop intentionally and observe that UI stops updating when parent state changes.
Solid's approach isn't universally superior—it trades the simplicity of a unified re-render model for granular control. For teams comfortable with reactive primitives and willing to adopt the "run-once" mindset, it eliminates an entire class of performance optimization work. For others, the learning curve and debugging differences may outweigh the raw speed gains.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.