SolidJS Performance: Optimization Techniques and Benchmarks
SolidJS has earned a reputation as one of the fastest JavaScript UI frameworks available today. Unlike virtual DOM-based frameworks, SolidJS compiles templates to real DOM nodes and uses fine-grained reactivity to update only what changes. However, even with these architectural advantages, developers must understand how to leverage SolidJS's primitives correctly to extract maximum performance. This tutorial explores what makes SolidJS fast, why optimization matters, and how to apply proven techniques in real applications.
What Makes SolidJS Fast
SolidJS avoids the virtual DOM entirely. Instead of diffing trees on every state change, it tracks dependencies at the signal level. When a signal updates, only the specific DOM node or expression bound to that signal re-runs. This fine-grained reactivity means updates are O(1) relative to the size of the application, not O(n) like diff-based frameworks.
Key architectural pillars include:
- Signals: Reactive primitives that hold state and notify subscribers on change.
- Memos: Cached derived values that only recompute when dependencies change.
- Effects: Side effects that automatically track signal reads within their scope.
- Compiled templates: JSX compiles to direct DOM operations, not virtual nodes.
Why Performance Optimization Matters
Even with SolidJS's efficient core, poorly structured reactivity can cause unnecessary recomputations, memory leaks, or janky rendering in large applications. Optimization matters because:
- Large lists with thousands of items can still cause bottlenecks if not virtualized.
- Overusing reactive primitives creates excessive subscriptions and memory overhead.
- Improper store updates can trigger cascading re-renders across the component tree.
- Bundle size and hydration time affect first contentful paint on mobile devices.
Understanding when and how to optimize ensures your application remains responsive as it scales.
How to Use SolidJS Optimization Techniques
1. Prefer Signals Over Stores for Primitive State
Signals are the lightest reactive primitive in SolidJS. For simple primitive values like booleans, numbers, or strings, signals outperform stores because they have fewer moving parts.
import { createSignal } from "solid-js";
function Counter() {
const [count, setCount] = createSignal(0);
return (
<button onClick={() => setCount(c => c + 1)}>
Clicked {count()} times
</button>
);
}
Use stores only when dealing with nested objects or arrays where granular updates are beneficial.
2. Use createMemo for Expensive Derived Computations
When a computed value depends on signals and is expensive to calculate, wrap it in createMemo. This caches the result and only recomputes when dependencies change.
import { createSignal, createMemo } from "solid-js";
function FilteredList() {
const [items, setItems] = createSignal(Array.from({ length: 10000 }, (_, i) => i));
const [query, setQuery] = createSignal("");
const filtered = createMemo(() => {
const q = query().toLowerCase();
return items().filter(item => String(item).includes(q));
});
return (
<>
<input
type="text"
value={query()}
onInput={e => setQuery(e.currentTarget.value)}
/>
<ul>
{filtered().map(item => <li>{item}</li>)}
</ul>
</>
);
}
Without createMemo, the filter would re-run on every signal read in the component, not just when items or query change.
3. Use For Instead of Index or Map for Lists
SolidJS provides the <For> component for efficient list rendering. Unlike .map(), <For> tracks items by reference and only updates the DOM nodes for items that actually change.
import { For, createSignal } from "solid-js";
function TodoList() {
const [todos, setTodos] = createSignal([
{ id: 1, text: "Learn SolidJS" },
{ id: 2, text: "Build an app" },
]);
return (
<ul>
<For each={todos()}>
{(todo) => <li>{todo.text}</li>}
</For>
</ul>
);
}
Avoid <Index> unless you specifically need to track by index position, as it re-renders items when the array order changes.
4. Virtualize Large Lists
For lists with thousands of items, use a virtualization library like solid-virtual to render only visible items.
import { createVirtualizer } from "@tanstack/solid-virtual";
import { For } from "solid-js";
function HugeList() {
let parentRef;
const rowVirtualizer = createVirtualizer({
count: 100000,
getScrollElement: () => parentRef,
estimateSize: () => 40,
});
return (
<div
ref={parentRef}
style={{ height: "500px", overflow: "auto" }}
>
<div
style={{
height: `${rowVirtualizer.getTotalSize()}px`,
position: "relative",
}}
>
<For each={rowVirtualizer.getVirtualItems()}>
{(virtualItem) => (
<div
style={{
position: "absolute",
top: `${virtualItem.start}px`,
height: `${virtualItem.size}px`,
}}
>
Row {virtualItem.index}
</div>
)}
</For>
</div>
</div>
);
}
5. Batch Updates with Stores
SolidJS stores allow fine-grained updates to nested data. When updating multiple properties, use the producer function form to batch changes into a single update cycle.
import { createStore } from "solid-js/store";
function UserProfile() {
const [user, setUser] = createStore({
name: "Alice",
age: 30,
address: { city: "Berlin", zip: "10115" },
});
const updateProfile = () => {
setUser("name", "Bob");
setUser("age", 31);
setUser("address", "city", "Munich");
};
return (
<div>
<p>{user.name}, {user.age}, {user.address.city}</p>
<button onClick={updateProfile}>Update</button>
</div>
);
}
6. Use on() to Control Effect Dependencies
By default, effects track every signal read inside them. Use the on helper to explicitly declare dependencies and prevent unintended tracking.
import { createSignal, createEffect, on } from "solid-js";
function Logger() {
const [a, setA] = createSignal(0);
const [b, setB] = createSignal(0);
// Only runs when `a` changes, even though `b` is read inside
createEffect(on(a, () => {
console.log("a changed:", a(), "b is:", b());
}));
return (
<>
<button onClick={() => setA(a() + 1)}>Inc A</button>
<button onClick={() => setB(b() + 1)}>Inc B</button>
</>
);
}
7. Defer Non-Critical Effects
Use createDeferred to delay expensive computations until the browser is idle, keeping the UI responsive during rapid updates.
import { createSignal, createDeferred } from "solid-js";
function SearchResults() {
const [query, setQuery] = createSignal("");
const deferredQuery = createDeferred(query, { timeoutMs: 100 });
return (
<>
<input onInput={e => setQuery(e.currentTarget.value)} />
<ExpensiveResultsView query={deferredQuery()} />
</>
);
}
Benchmarks: SolidJS vs Other Frameworks
The widely cited JS Framework Benchmark consistently places SolidJS near the top. Key metrics from recent runs include:
- Startup time: SolidJS ranks among the fastest, often within 5-10% of vanilla JS.
- Memory usage: Significantly lower than React and Vue due to no virtual DOM overhead.
- Update performance: Partial updates are nearly as fast as vanilla DOM manipulation.
- Bundle size: The core runtime is approximately 7KB minified and gzipped.
These benchmarks demonstrate that SolidJS's architecture delivers real-world performance benefits, not just theoretical improvements.
Best Practices
- Keep components small and focused. SolidJS does not re-run components on state change, so component size mainly affects initial render.
- Avoid creating signals inside loops. Hoist signal creation to parent scope and pass values via props or context.
- Use
untrackto read signals without subscribing. This prevents unwanted dependencies in effects. - Prefer
Showover conditional expressions for expensive subtrees.<Show>avoids rendering the hidden branch entirely. - Profile with the SolidJS DevTools. Identify unnecessary recomputations and memory leaks early.
- Lazy-load routes and heavy components using
lazy()andSuspenseto reduce initial bundle size.
import { lazy, Suspense } from "solid-js";
const HeavyChart = lazy(() => import("./HeavyChart"));
function App() {
return (
<Suspense fallback={<p>Loading...</p>}>
<HeavyChart />
</Suspense>
);
}
Conclusion
SolidJS provides an exceptional performance foundation through fine-grained reactivity and compiled templates, but achieving optimal results still requires deliberate engineering. By choosing the right reactive primitives, memoizing expensive computations, virtualizing large lists, batching store updates, and controlling effect dependencies, you can build applications that remain fast and responsive at any scale. Combine these techniques with regular profiling and benchmarking, and SolidJS will reward you with some of the best performance available in the modern frontend ecosystem.