solid-js
$
npx mdskill add TerminalSkills/skills/solid-jsBuild high-performance reactive UIs with SolidJS and SolidStart
- Create fast UIs without virtual DOM overhead
- Uses SolidJS 1.x/2.x and SolidStart 1.x with Node.js 18+
- Leverages signals, effects, and stores for fine-grained reactivity
- Enables full-stack SSR apps with server functions and React-like patterns
SKILL.md
.github/skills/solid-jsView on GitHub ↗
---
name: solid-js
description: >-
SolidJS — fine-grained reactive UI library without a virtual DOM. Use when
building high-performance UIs, choosing a React alternative with better
reactivity primitives, or using SolidStart for full-stack apps with SSR and
server functions. Covers signals, effects, memos, stores, and migration
patterns from React.
license: Apache-2.0
compatibility: "SolidJS 1.x / 2.x. SolidStart 1.x. Requires Node.js 18+ for build tooling."
metadata:
author: terminal-skills
version: "1.0.0"
category: development
tags: ["solidjs", "reactive", "signals", "performance", "frontend"]
use-cases:
- "Build a high-performance UI without virtual DOM overhead"
- "Migrate a React component to SolidJS for better reactivity"
- "Use SolidStart for a full-stack app with SSR and server functions"
- "Manage complex application state with SolidJS stores"
agents: [claude-code, openai-codex, gemini-cli, cursor]
---
# SolidJS
## Overview
SolidJS is a declarative UI library that compiles JSX to real DOM operations — no virtual DOM, no diffing. Reactivity is fine-grained: only the exact DOM nodes that depend on changed data update. The result is near-native performance with a React-like authoring experience.
## Installation
```bash
# New SolidJS project
npx degit solidjs/templates/ts my-app
cd my-app
npm install
npm run dev
# Or with Vite
npm create vite@latest my-app -- --template solid-ts
```
## Reactivity Primitives
### createSignal — Reactive State
```typescript
import { createSignal } from "solid-js";
// Signals are getter/setter pairs
const [count, setCount] = createSignal(0);
// Read: call as function
console.log(count()); // 0
// Write: call setter
setCount(1);
setCount((prev) => prev + 1); // Updater function
// Signals with objects
const [user, setUser] = createSignal({ name: "Alice", age: 30 });
setUser((prev) => ({ ...prev, age: 31 }));
```
### createEffect — Side Effects
```typescript
import { createSignal, createEffect } from "solid-js";
const [name, setName] = createSignal("Alice");
// Runs immediately, then reruns whenever dependencies change
createEffect(() => {
console.log("Name changed:", name());
document.title = `Hello, ${name()}!`;
});
// Cleanup function
createEffect(() => {
const id = setInterval(() => console.log(name()), 1000);
return () => clearInterval(id); // Runs before next effect or on dispose
});
```
### createMemo — Derived State
```typescript
import { createSignal, createMemo } from "solid-js";
const [price, setPrice] = createSignal(100);
const [quantity, setQuantity] = createSignal(3);
// Computed value — only recalculates when dependencies change
const total = createMemo(() => price() * quantity());
console.log(total()); // 300
setPrice(120);
console.log(total()); // 360 — recalculated
```
## Control Flow
```typescript
import { Show, For, Switch, Match, Index } from "solid-js";
// Show — conditional rendering
<Show when={isLoggedIn()} fallback={<LoginForm />}>
<Dashboard />
</Show>
// For — list rendering (efficient keyed updates)
<For each={items()}>
{(item, index) => <div>{index()} - {item.name}</div>}
</For>
// Index — list rendering when items change in place (not reorder)
<Index each={items()}>
{(item, index) => <div>{index} - {item().name}</div>}
</Index>
// Switch/Match — multi-branch conditional
<Switch fallback={<p>Unknown status</p>}>
<Match when={status() === "loading"}><Spinner /></Match>
<Match when={status() === "error"}><ErrorView /></Match>
<Match when={status() === "success"}><DataView /></Match>
</Switch>
```
## Stores — Complex State
```typescript
import { createStore, produce } from "solid-js/store";
const [state, setState] = createStore({
users: [] as { id: number; name: string; active: boolean }[],
loading: false,
});
// Update nested properties
setState("loading", true);
setState("users", 0, "active", false);
// Immer-like mutations with produce
setState(
produce((s) => {
s.users.push({ id: 4, name: "Diana", active: true });
s.loading = false;
})
);
```
## Resources — Async Data Fetching
```typescript
import { createSignal, createResource, Suspense } from "solid-js";
// createResource — wraps async data fetching
async function fetchUser(id: number) {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error("User not found");
return res.json();
}
function UserProfile() {
const [userId, setUserId] = createSignal(1);
// Refetches automatically when userId() changes
const [user, { refetch, mutate }] = createResource(userId, fetchUser);
return (
<Suspense fallback={<p>Loading...</p>}>
{/* user() is undefined while loading, the value when ready */}
<Show when={user()} fallback={<p>Error loading user</p>}>
{(u) => (
<div>
<h1>{u().name}</h1>
<button onClick={refetch}>Refresh</button>
</div>
)}
</Show>
</Suspense>
);
}
```
## SolidStart — Full-Stack
SolidStart adds file-based routing, SSR, and server functions:
```bash
npm create solid@latest
# Choose: SolidStart, TypeScript
```
File structure:
```
src/
routes/
index.tsx → /
about.tsx → /about
users/
index.tsx → /users
[id].tsx → /users/:id
app.tsx
```
```typescript
// src/routes/users/[id].tsx
import { createAsync, useParams } from "@solidjs/router";
import { Show, Suspense } from "solid-js";
import { getUser } from "~/lib/users"; // server function
export default function UserPage() {
const params = useParams();
const user = createAsync(() => getUser(Number(params.id)));
return (
<Suspense fallback={<p>Loading...</p>}>
<Show when={user()}>
{(u) => (
<article>
<h1>{u().name}</h1>
<p>{u().email}</p>
</article>
)}
</Show>
</Suspense>
);
}
```
```typescript
// src/lib/users.ts — server functions
"use server";
export async function getUser(id: number) {
const res = await fetch(`https://api.example.com/users/${id}`);
return res.json();
}
export async function createUser(data: { name: string; email: string }) {
// Runs on the server only — safe to access DB
return db.insert(users).values(data).returning();
}
```
## Context (Dependency Injection)
```typescript
import { createContext, useContext, type ParentComponent } from "solid-js";
import { createStore } from "solid-js/store";
const AuthContext = createContext<{ user: () => User | null; login: (u: User) => void }>();
export const AuthProvider: ParentComponent = (props) => {
const [state, setState] = createStore<{ user: User | null }>({ user: null });
const value = { user: () => state.user, login: (u: User) => setState("user", u) };
return <AuthContext.Provider value={value}>{props.children}</AuthContext.Provider>;
};
export const useAuth = () => useContext(AuthContext)!;
```
## Migrating from React
| React | SolidJS |
|---|---|
| `useState(0)` | `createSignal(0)` |
| `useEffect(() => {}, [dep])` | `createEffect(() => { dep(); })` |
| `useMemo(() => calc, [dep])` | `createMemo(() => calc())` |
| `useReducer` | `createStore` |
| `useContext` | `useContext` (same API) |
| `React.memo` | Not needed — no re-renders |
| `key` prop in lists | Use `<For>` instead of `map()` |
| `useRef` | `createSignal` or `let el!: HTMLElement` |
Key differences:
- **Components run once** — no "re-render" concept. Put reactive logic inside JSX or effects.
- **Access signals by calling them** — `count()` not `count`.
- **Use `<For>` for lists** — not `.map()` — for efficient keyed rendering.
- **Destructuring signals breaks reactivity** — pass signals or use stores.
## Guidelines
- Never destructure signals: `const { x } = state` breaks reactivity. Use `state.x` or `createMemo`.
- Use `<For>` instead of `Array.map` in JSX for efficient list rendering.
- Components run once — initialize logic at the top level, not in callbacks.
- Use `createStore` for objects/arrays that need fine-grained updates.
- Use `createResource` for async data — it integrates with `<Suspense>` automatically.
- `createMemo` is cached — prefer it over calling `createEffect` to compute derived values.
- SolidStart `"use server"` functions run exclusively on the server — safe for DB access.
More from TerminalSkills/skills