L’interface est localisée ; les présentations et documents des Skills sont traduits automatiquement par IA dans votre langue. Le code, les commandes, chemins et termes techniques restent en version originale, et le document de l’auteur fait foi.
Explorer les Skills
Productivité · SKILL.md

solid-js

solid-js

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.

Independent GitHub snapshot · Automated classification · Original documentation governs

Tterminalskills / skillsApache-2.0

Documentation de la Skill

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.9 (stable; 2.0 is a release candidate). Vite 8 needs Node.js 20.19+ or 22.12+; SolidStart 2 needs Node.js 24+." metadata: author: terminal-skills version: "1.1.0" category: development tags: ["solidjs", "reactive", "signals", "performance", "frontend"] repository: https://github.com/solidjs/solid 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.

Instructions

Installation

# Official scaffolder — prompts for template, TypeScript and SolidStart
npm create solid@latest
# Same thing without prompts: a client-rendered Solid + Vite app
npm create solid@latest price-table -- --vanilla --ts -t basic
# Or the Vite template
npm create vite@latest price-table -- --template solid-ts
cd price-table && npm install && npm run dev

createSignal — Reactive State

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

createEffect — Side Effects

import { createSignal, createEffect, onCleanup } from "solid-js";

const [name, setName] = createSignal("Alice");
// First run happens after the component has rendered, then again whenever name() changes
createEffect(() => {
  document.title = `Hello, ${name()}!`;
});

// Cleanup goes through onCleanup; a returned value is passed to the next run, not called
createEffect(() => {
  const id = setInterval(() => console.log(name()), 1000);
  onCleanup(() => clearInterval(id)); // Runs before the next run and on dispose
});

createMemo — Derived State

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()); // 300
setPrice(120); // total() is now 360

Control Flow

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

import { createStore, produce } from "solid-js/store";

const [state, setState] = createStore({
  users: [{ id: 1, name: "Alice", active: true }],
  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

import { createSignal, createResource, ErrorBoundary, Show, Suspense } from "solid-js";

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);
  // A rejected fetcher surfaces in the nearest ErrorBoundary and as user.error; user.loading is also available
  return (
    <ErrorBoundary fallback={(err) => <p>Failed: {err.message}</p>}>
      <Suspense fallback={<p>Loading...</p>}>
        <Show when={user()}>{(u) => <h1>{u().name}</h1>}</Show>
      </Suspense>
    </ErrorBoundary>
  );
}

SolidStart — Full-Stack

SolidStart adds file-based routing, SSR, and server functions. SolidStart 2 (August 2026) builds with Vite and Nitro 3 instead of Vinxi and requires Node.js 24 or newer:

npm create solid@latest task-board -- --solidstart --v2 --ts -t basic
cd task-board && npm install && npm run dev

Routes are files: src/routes/index.tsx → /, src/routes/users/[id].tsx → /users/:id, with the shell in src/app.tsx. Configuration lives in vite.config.ts (plugins: [solidStart(), nitro()], imported from @solidjs/start/config and nitro/vite).

Data loading and mutations come from @solidjs/router: wrap reads in query, read them with createAsync, and wrap writes in action (Example 2). Upgrading a v1 app: replace the vinxi scripts with vite dev / vite build, move app.config.ts settings into solidStart() in vite.config.ts, and import HTTP helpers from @solidjs/start/http instead of vinxi/http.

Context (Dependency Injection)

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

ReactSolidJS
useState(0)createSignal(0)
useEffect(() => {}, [dep])createEffect(() => { dep(); }) — dependencies are tracked automatically
effect cleanup return () => …onCleanup(() => …) inside the effect
useMemo(() => calc, [dep])createMemo(() => calc())
useReducercreateStore
useContextuseContext (same API)
React.memoNot needed — no re-renders
key prop in listsUse <For> instead of map()
useReflet el!: HTMLDivElement with <div ref={el}>

Examples

Example 1: Port a React component

User request: "Port our React cart to Solid — it keeps the lines in useState, the subtotal in useMemo, and a useEffect sets the page title."

import { createEffect, createMemo, For } from "solid-js";
import { createStore } from "solid-js/store";
type Line = { sku: string; title: string; price: number; qty: number };

export function Cart(props: { lines: Line[]; taxRate: number }) {
  const [lines, setLines] = createStore(props.lines);
  const subtotal = createMemo(() => lines.reduce((sum, l) => sum + l.price * l.qty, 0));
  const total = () => subtotal() * (1 + props.taxRate);
  createEffect(() => {
    document.title = `Cart (${lines.length}) — ${total().toFixed(2)}`;
  });
  return (
    <section>
      <For each={lines} fallback={<p>Your cart is empty.</p>}>
        {(line, i) => (
          <label>
            {line.title}
            <input type="number" min="0" value={line.qty}
              onInput={(e) => setLines(i(), "qty", e.currentTarget.valueAsNumber || 0)} />
          </label>
        )}
      </For>
      <p>Total: ${total().toFixed(2)}</p>
    </section>
  );
}

Result: Cart runs once. Changing a quantity updates that line's qty, the total text node and the document title — nothing else is re-created, and no dependency arrays are needed.

Example 2: Server-backed page in SolidStart

User request: "Add a /tasks page to the SolidStart app that lists tasks from the server and has a form to add one."

// src/lib/tasks.ts
import { action, query, redirect } from "@solidjs/router";
type Task = { id: number; title: string; done: boolean };
const tasks: Task[] = [{ id: 1, title: "Write release notes", done: false }]; // stand-in for a database

export const getTasks = query(async () => {
  "use server"; // inside the function, not at the top of the file, when wrapped in query/action
  return tasks;
}, "tasks");

export const addTask = action(async (formData: FormData) => {
  "use server";
  const title = String(formData.get("title") ?? "").trim();
  if (!title) throw new Error("Title is required");
  tasks.push({ id: tasks.length + 1, title, done: false });
  throw redirect("/tasks");
}, "addTask");
// src/routes/tasks.tsx
import { For, Show } from "solid-js";
import { createAsync, useSubmission, type RouteDefinition } from "@solidjs/router";
import { addTask, getTasks } from "~/lib/tasks";

export const route = { preload: () => getTasks() } satisfies RouteDefinition;
export default function TasksPage() {
  const tasks = createAsync(() => getTasks());
  const adding = useSubmission(addTask);
  return (
    <main>
      <ul><For each={tasks()}>{(task) => <li>{task.title}</li>}</For></ul>
      <form action={addTask} method="post">
        <input name="title" required />
        <button disabled={adding.pending}>Add task</button>
      </form>
      <Show when={adding.error}>{(err) => <p>{err().message}</p>}</Show>
    </main>
  );
}

Result: /tasks is server-rendered with the Write release notes list item already in the HTML (the <li> carries a data-hk hydration attribute). Submitting the form posts to a generated /_server?id=… endpoint, answers 302 with location: /tasks, and the list re-renders with the new task; it also works before JavaScript has loaded.

Guidelines

  • Never destructure props or stores: const { x } = props reads the value once and breaks reactivity. Use props.x, splitProps, or createMemo.
  • Use <For> instead of Array.map in JSX for efficient list rendering.
  • Components run once — put reactive reads inside JSX, memos, or effects, not in the component body.
  • createMemo is cached — prefer it over a createEffect that writes to another signal.
  • Effects never run during server rendering; data needed for SSR belongs in a resource or a router query.
  • "use server" code runs only on the server, but each function becomes a public HTTP endpoint — check the session and validate arguments inside it.
  • Solid 2.0 is still a release candidate (solid-js@next) and is not a drop-in upgrade: DOM APIs move to @solidjs/web, store helpers move into solid-js, createEffect splits into compute and apply functions, Suspense/ErrorBoundary become Loading/Errored, Index becomes <For keyed={false}>, and createResource is replaced by async memos. Everything above targets 1.9; read the 2.0 migration guide in the next branch before adopting it.
  • React component libraries do not work in Solid; if a project depends on them, stay on React or look for a Solid port first.
Skills enregistrées