mrkeyoor.com_
Sat 08 Aug 21:56 UTC
npmWeb Frontendupdated 08 Aug 2026

solid-js

Solid is a component library for web interfaces that compiles JSX into real DOM creation and targeted updates. It has no virtual DOM rerender pass: a component function normally runs once to build its view, while signals, memos, resources, and stores track exactly which expressions depend on changing state. The core package also supplies context, Suspense, error boundaries, portals, server rendering, hydration, and a universal renderer API. It feels familiar at the JSX level, but its accessor-based reactivity is materially different from React's component lifecycle.

Verdict

Solid is one of the best choices for teams that want JSX with precise DOM updates and are willing to relearn reactivity. Do not choose it as a drop-in React substitute; the syntax travels more easily than the mental model.

API stability4/5Solid has remained on the 1.x line while its central contracts, including createSignal accessors, one-time component setup, fine-grained tracking, control-flow components, stores, resources, and solid-js/web rendering, have stayed consistent. The package publishes explicit conditional exports and bundled declarations. Framework-sensitive behavior still depends on compiler and server/client conditions, so upgrades need build and hydration tests rather than only type checking.
Docs5/5The official documentation resolves correctly and covers concepts, tutorials, guides, reference material, server rendering, and framework use. The README gives a working counter, shows the compiled DOM output, explains the render-once model, lists browser and server support, and links a playground. The strongest part is that the docs teach why signal accessors and control-flow helpers exist instead of presenting APIs as isolated recipes.
Maintenance5/5Version 1.9.14 was published in July 2026, the repository was pushed on August 7, 2026, and it is neither archived nor disabled. GitHub reports 30 open issues and pull requests against a project with 35,812 stars, while the README points to active core-team support and current browser and server targets. Releases, documentation, compiler integration, and ecosystem packages all show ongoing work rather than passive upkeep.
Ecosystem4/5The package recorded 3,383,511 downloads in the measured week, has 35,812 GitHub stars, integrates with Vite and TypeScript, and links established community collections for primitives and component libraries. It covers client rendering, SSR, hydration, custom elements, and custom renderers. The ecosystem is healthy, but vendor SDKs, design systems, native targets, and available specialists remain less plentiful than in React's much larger market.

Use it if

  • You want JSX and component composition without virtual DOM rerenders
  • Your interface updates frequently enough that fine-grained subscriptions and direct DOM updates are valuable
  • You want signals, stores, resources, Suspense, context, portals, SSR, and hydration in one small core package
  • Your team is comfortable learning accessor-based state and compiler-specific JSX semantics
Skip it if

Setup reality

For a client app, use a Solid-aware Vite template or install solid-js with vite-plugin-solid; installing the runtime alone does not teach a generic JSX compiler Solid's update model. A manual Vite setup needs the Solid plugin in vite.config.ts and TypeScript compilerOptions with jsx set to preserve and jsxImportSource set to solid-js. There are no peer dependencies, credentials, native builds, or runtime config files, and 1.9.14 has three direct dependencies. Bundlephobia reports 8.357 KB gzipped for the main package, but routing, deployment, testing, and a production SSR stack are separate choices. The first surprise is semantic: createSignal returns an accessor and setter, so read count(), not count; component functions normally execute once; props are reactive getters that should not be casually destructured; and effects track synchronous reads, not values first accessed after an await or timer. Use createMemo for expensive derived work, onCleanup for subscriptions, and For or Index rather than Array.map when a list must update reactively. Import DOM rendering from solid-js/web, stores from solid-js/store, and core primitives from solid-js. Conditional exports intentionally select server implementations under Node, so use the compiler and supported bundler conditions instead of deep-importing dist files. SSR also requires matching server and client transforms and hydrate() rather than render(); most teams should use the SolidStart framework rather than assemble streaming SSR, routing, serialization, and deployment adapters by hand. Supported targets are modern browsers, Node LTS, recent Deno, and Cloudflare Workers, not IE.

Patterns

Create reactive state and mount a componentrender-counter

import { createSignal } from 'solid-js';
import { render } from 'solid-js/web';

function Counter() {
  const [count, setCount] = createSignal(0);
  return (
    <button onClick={() => setCount((n) => n + 1)}>
      Count: {count()}
    </button>
  );
}

render(() => <Counter />, document.getElementById('app')!);

count is an accessor and must be called; passing count itself gives the reactive expression to Solid, while count() reads its current value.

Cache expensive derived statederive-memo

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

const [items, setItems] = createSignal<Product[]>([]);
const total = createMemo(() =>
  items().reduce((sum, item) => sum + item.price, 0)
);

<p>Total: {total()}</p>

A plain function is enough for cheap derived values; createMemo adds caching and only recomputes when tracked dependencies change.

React to one explicit dependencyrun-tracked-effect

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

const [query, setQuery] = createSignal('');
createEffect(on(query, (value, previous) => {
  console.log('query changed', previous, value);
}, { defer: true }));

on() makes dependencies explicit, and defer skips the initial run; reads that happen later inside an async callback are not automatically tracked.

Render a keyed reactive listrender-list

import { For } from 'solid-js';

<ul>
  <For each={todos()} fallback={<li>No tasks</li>}>
    {(todo, index) => <li>{index() + 1}. {todo.title}</li>}
  </For>
</ul>

For keys rows by item identity and gives index as a signal; prefer Index when positions are stable but values at those positions change.

Show content with a fallbackrender-condition

import { Show } from 'solid-js';

<Show when={currentUser()} fallback={<a href="/login">Sign in</a>}>
  {(user) => <p>Welcome, {user().name}</p>}
</Show>

The callback form narrows the value and keeps access reactive; storing currentUser() in an ordinary variable outside JSX would capture one read.

Fetch data from a reactive sourceload-resource

import { createResource, Suspense } from 'solid-js';

const [userId, setUserId] = createSignal('42');
const [user, { refetch }] = createResource(userId, async (id) => {
  const response = await fetch(`/api/users/${id}`);
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return response.json();
});

<Suspense fallback={<p>Loading...</p>}>
  <p>{user()?.name}</p>
</Suspense>

The fetcher reruns when userId changes; fetch does not reject on HTTP errors, so check response.ok yourself.

Update nested state with a storeupdate-store

import { createStore } from 'solid-js/store';

const [state, setState] = createStore({
  users: [{ id: 1, name: 'Ada', active: false }],
});

setState('users', (user) => user.id === 1, 'active', true);
<p>{state.users[0].active ? 'Active' : 'Inactive'}</p>

Store values are proxies; use setState paths or produce-style helpers instead of mutating nested properties directly.

Split props without losing reactivitypreserve-reactive-props

import { splitProps, type ComponentProps } from 'solid-js';

type Props = { label: string } & ComponentProps<'button'>;

function ActionButton(props: Props) {
  const [local, buttonProps] = splitProps(props, ['label']);
  return <button {...buttonProps}>{local.label}</button>;
}

Solid props are reactive getters; ordinary const { label } = props reads once and can prevent later parent updates from reaching the text.

Create a typed context with a guarded hookprovide-context

import { createContext, createSignal, useContext, type ParentProps } from 'solid-js';

const ThemeContext = createContext<ReturnType<typeof createSignal<string>>>();

export function ThemeProvider(props: ParentProps) {
  const theme = createSignal('light');
  return <ThemeContext.Provider value={theme}>{props.children}</ThemeContext.Provider>;
}

export function useTheme() {
  const value = useContext(ThemeContext);
  if (!value) throw new Error('ThemeProvider is missing');
  return value;
}

Without a provider, useContext returns the createContext default; throwing in a helper catches misplaced consumers early.

Dispose an external subscriptionclean-up-subscription

import { onCleanup, onMount } from 'solid-js';

function OnlineStatus() {
  const [online, setOnline] = createSignal(navigator.onLine);
  onMount(() => {
    const update = () => setOnline(navigator.onLine);
    window.addEventListener('online', update);
    window.addEventListener('offline', update);
    onCleanup(() => {
      window.removeEventListener('online', update);
      window.removeEventListener('offline', update);
    });
  });
  return <span>{online() ? 'Online' : 'Offline'}</span>;
}

onCleanup runs when the owning component or reactive scope is disposed; register it in the same owner that created the subscription.

Lazy-load a route-sized componentlazy-load-component

import { lazy, Suspense } from 'solid-js';

const Reports = lazy(() => import('./Reports'));

<Suspense fallback={<p>Loading reports...</p>}>
  <Reports />
</Suspense>

The imported module must default-export a component; Suspense supplies the fallback while the code chunk and nested resources resolve.

Recover from a subtree errorcatch-render-errors

import { ErrorBoundary } from 'solid-js';

<ErrorBoundary fallback={(error, reset) => (
  <section>
    <p>{error.message}</p>
    <button onClick={reset}>Try again</button>
  </section>
)}>
  <Dashboard />
</ErrorBoundary>

An error boundary catches rendering and reactive-scope failures below it, but it does not replace normal error handling in unrelated event handlers.

Alternatives

PackageRegistryPick it when
reactnpmYou prioritize the largest component, integration, and hiring ecosystem over fine-grained updates
vuenpmYou want fine-grained reactivity with a more batteries-included template and official tooling story
sveltenpmYou prefer compiler-driven components with concise single-file component syntax and signals called runes