EmpireUI
Get Pro
← Blog8 min read#solidjs#reactivity#performance

SolidJS Introduction: Fine-Grained Reactivity Without a VDOM

SolidJS skips the virtual DOM entirely and surgically updates only what changed. Here's how fine-grained reactivity works and why it's genuinely fast.

abstract glowing circuit lines representing fine-grained reactive data flow

What SolidJS Actually Is (And Why You Should Care)

SolidJS is a JavaScript UI library that compiles your JSX to real DOM operations at build time — no virtual DOM, no diffing, no reconciler. You write components that look almost identical to React. But what the browser actually runs is radically different.

Honestly, the first time you look at a SolidJS benchmark result you think something's wrong. It routinely sits within 5–10% of vanilla JavaScript on js-framework-benchmark, which is the kind of result that makes framework authors a little uncomfortable. React, Vue, and Svelte all have overhead SolidJS just doesn't have.

The core primitive is the *signal* — a reactive value that knows exactly which parts of the DOM depend on it. When the signal changes, those nodes update. Nothing else does. No component re-render, no tree walk, no diffing pass. That's the whole trick, and it's been in use since Ryan Carniato started building Solid back around 2018.

Worth noting: SolidJS 1.8 (released in 2024) stabilized the store API and made async transitions first-class. If you last looked at Solid two years ago, it's matured a lot.

Signals, Effects, and Memos — The Reactive Primitives

Everything in SolidJS starts with createSignal. It returns a getter and a setter — yes, two separate functions, not a tuple with .value. This is intentional. The getter is what creates the reactive subscription; calling it inside a reactive context (an effect, a memo, JSX) tells Solid "this computation depends on this value."

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

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

  // runs whenever count() changes
  createEffect(() => {
    console.log('count is now', count());
  });

  // derived value, only recomputes when count() changes
  const doubled = createMemo(() => count() * 2);

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

createMemo is your computed/derived value. It caches its result and only re-evaluates when its dependencies change. Think of it like useMemo but without the dependency array — Solid tracks dependencies automatically by watching which signals you read during execution.

createEffect replaces useEffect. No dependency array here either. Solid figures it out. In practice, this eliminates an entire category of bugs: stale closures and forgotten dependencies simply don't exist.

One more thing — unlike React's hooks, you can call createSignal, createEffect, and createMemo conditionally, inside loops, wherever. The rules of hooks don't apply because these aren't hooks. They're just function calls that register reactive computations.

How Fine-Grained Reactivity Differs From VDOM Diffing

In React, when state changes, the component function re-runs from the top. React then computes what the new VDOM tree looks like, diffs it against the previous tree, and patches the real DOM. That whole pipeline runs on every state change. For most apps it's fast enough. But it's doing a lot of work you might not need.

SolidJS flips the model. Your component function runs *once* — at mount time. JSX compiles to actual DOM creation code, not VDOM nodes. When a signal changes, Solid runs only the fine-grained effects that depend on it. If your button label depends on a count signal and nothing else does, exactly one text node gets updated. The rest of the DOM is untouched.

// What SolidJS compiles JSX to (roughly):
const _el = document.createElement('p');
createEffect(() => {
  _el.textContent = count(); // only this runs on change
});

This means your component function is not a render function in the React sense. It's closer to a setup function — it runs once, wires up reactivity, and gets out of the way. That mental shift is the main thing you need to internalize when moving from React to Solid.

Quick aside: this is also why SolidJS doesn't need React.memo, useCallback, or useMemo as optimization escape hatches. Because components don't re-render, there's nothing to memoize at the component boundary. createMemo is still useful for expensive derived computations, but you won't be sprinkling it everywhere defensively.

Writing Components: JSX That Feels Like React (But Isn't)

The surface syntax is close enough to React that you can read Solid code immediately. Props, children, event handlers — it all looks familiar. The differences show up in control flow.

Solid ships <Show>, <For>, <Switch>, and <Match> as built-in JSX components for conditional rendering and list iteration. You don't use ternaries or .map() — or rather, you *can*, but those won't be reactive in the way you expect. The built-in components are what give Solid fine-grained control over those parts of the DOM.

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

function UserList() {
  const [users, setUsers] = createSignal([
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' },
  ]);
  const [showList, setShowList] = createSignal(true);

  return (
    <div>
      <button onClick={() => setShowList(s => !s)}>Toggle</button>

      <Show when={showList()} fallback={<p>List hidden</p>}>
        <ul>
          <For each={users()}>
            {(user) => <li>{user.name}</li>}
          </For>
        </ul>
      </Show>
    </div>
  );
}

Look, the <For> component is important to use correctly. It takes a keyed list and only updates the DOM nodes that actually changed. If you add an item to the beginning of the array, only one new <li> gets created — the existing ones don't re-render. That's 60px of scroll position preserved, no flicker, no layout shift.

Stores (via createStore) handle nested reactive objects. They use ES6 Proxies to make deep property access reactive without you having to manually spread or clone objects. It's similar in concept to Vue 3's reactive system, but Solid's is arguably more predictable because the tracking is purely pull-based.

Performance: Concrete Numbers and Real-World Tradeoffs

On the js-framework-benchmark suite (v1.0, Chrome, mid-range hardware), SolidJS consistently scores sub-2ms for most individual operations — select row, swap rows, remove row. React 18 with concurrent features enabled is typically 2–4x slower on those same operations. That's not a knock on React; it's doing more work by design.

Where Solid really pulls ahead is memory. No VDOM means no tree of virtual nodes sitting in memory alongside the real DOM. For large tables or long lists — the kind you'd see in a data-heavy analytics dashboard — that difference becomes tangible on devices with less than 4 GB of RAM.

In practice, the performance gap matters most in two scenarios: frequently-updating data (live prices, real-time feeds, collaborative cursors) and very large initial renders where VDOM creation overhead compounds. For a standard marketing page or modest SaaS UI, you won't feel the difference.

That said, bundle size is worth mentioning. SolidJS's runtime is around 7 KB gzipped in production — compared to React DOM at roughly 42 KB. If you're optimizing for Lighthouse performance scores, that difference alone can move the needle on initial page load, especially on slow 3G connections.

Setting Up a SolidJS Project in 2026

The fastest path is Vite with the Solid template. It's a two-command setup and you're writing components in under a minute.

npm create vite@latest my-solid-app -- --template solid-ts
cd my-solid-app
npm install
npm run dev

You get TypeScript, HMR, and a production build out of the box. The babel-plugin-solid (or vite-plugin-solid) handles JSX compilation — it transforms your JSX into direct DOM calls rather than React.createElement calls. Nothing extra to configure.

For routing, @solidjs/router is the official solution. It's been stable since Solid 1.6 and supports nested layouts, data loading with createResource, and file-based routing if you use SolidStart (the meta-framework, analogous to Next.js). createResource is particularly nice — it's a signal that wraps async data fetching, giving you loading/error states reactively without any library.

import { createResource } from 'solid-js';

async function fetchUser(id: string) {
  const res = await fetch(`/api/users/${id}`);
  return res.json();
}

function UserProfile(props: { id: string }) {
  const [user] = createResource(() => props.id, fetchUser);

  return (
    <div>
      <Show when={!user.loading} fallback={<p>Loading...</p>}>
        <h1>{user()?.name}</h1>
      </Show>
    </div>
  );
}

If you're building a design-heavy Solid app and want pre-built UI primitives, most Tailwind-based component libraries work fine since they're just CSS. Check out the gradient generator and box shadow generator — both output pure CSS you can drop straight into Solid components without any framework-specific wrappers.

When to Pick SolidJS Over React (Honest Assessment)

React's ecosystem is enormous. Next.js, the component library landscape, the job market — all of it skews React. You'd be choosing Solid against real ecosystem gravity, and you should go in clear-eyed about that.

Solid wins when performance is the primary constraint and you can afford a smaller ecosystem. Real-time dashboards, game UIs, financial data tools, collaborative whiteboards — anything where you're pushing 60fps updates through UI. It also wins when bundle size is non-negotiable, like embedded widgets or third-party scripts.

React wins when ecosystem compatibility matters more than raw speed. If you need React-specific animation libraries, server components, or a large team that already knows React, switching costs probably aren't worth it. There's no shame in that calculation.

In practice, the most common use case I'd reach for Solid is a standalone interactive island inside an otherwise static site — think a real-time price widget on a product page, or a live preview tool. You can ship a 7 KB Solid bundle for just that component without dragging React into the page at all. For component-heavy design work, Empire UI's browse components library and style hubs like neumorphism and cyberpunk give you a visual starting point you can adapt to Solid's DOM-first output.

FAQ

Does SolidJS use JSX?

Yes, but it compiles JSX to direct DOM operations, not React.createElement calls. You need the Solid Babel or Vite plugin — don't use the standard React JSX transform.

Can I use React hooks in SolidJS?

No. Solid has its own primitives: createSignal, createEffect, createMemo, createStore. They're conceptually similar but work differently — components run once, not on every state change.

Is SolidJS production-ready in 2026?

Yes. Solid 1.8+ is stable and used in production by several companies. SolidStart (the meta-framework) hit 1.0 in 2024 and covers SSR, file-based routing, and data loading.

How does SolidJS compare to Svelte?

Both compile away framework overhead, but Solid uses signals at runtime while Svelte generates imperative update code. Solid's reactivity is more granular; Svelte's compiler output can be simpler for straightforward cases.

Free components in 40 styles
React & Tailwind, copy-paste ready.
Browse →

Read next

Fine-Grained Reactivity vs Virtual DOM: What Actually Re-RendersQwik Framework: Resumability, Lazy Loading and Zero HydrationSolidJS vs React: Fine-Grained Reactivity vs Virtual DOMWeb Animations API in 2026: Native Animations Without a Library