Während React seit Jahren den Frontend-Markt dominiert, gewinnt SolidJS 2026 zunehmend an Bedeutung — und das aus gutem Grund. Der Kernunterschied liegt nicht nur in der Performance, sondern im fundamentalen Designprinzip: SolidJS verzichtet vollständig auf ein Virtual DOM und setzt stattdessen auf Fine-grained Reaktivität. Das Ergebnis ist ein Framework, das schneller ist als React, weniger RAM verbraucht und trotzdem eine sehr ähnliche Entwicklererfahrung bietet.
In diesem Artikel zeigen wir dir — anhand konkreter TypeScript-Beispiele — wie du SolidJS mit Claude Code produktiv nutzt: von den Grundprimitiven über komplexe Store-Mutations bis hin zu SolidStart für Full-Stack-Anwendungen.
1. SolidJS Grundprinzipien: Kein Virtual DOM
React rendert bei jeder State-Änderung den Komponenten-Baum neu und vergleicht das Ergebnis mit dem vorherigen Virtual DOM (Reconciliation). SolidJS geht einen anderen Weg: JSX wird beim Build-Schritt direkt zu imperativen DOM-Aufrufen kompiliert. Zur Laufzeit gibt es keinen Diffing-Algorithmus — Updates passieren chirurgisch, genau dort wo sie gebraucht werden.
Kernkonzept
Was passiert beim Kompilieren?
Aus diesem JSX:
const App = () => {
const [count, setCount] = createSignal(0);
return <button onClick={() => setCount(count() + 1)}>{count()}</button>;
};
...generiert der Compiler in etwa:
const App = () => {
const [count, setCount] = createSignal(0);
const _el = document.createElement("button");
_el.addEventListener("click", () => setCount(count() + 1));
// createEffect verknüpft count() mit dem Textknoten
createEffect(() => (_el.textContent = count()));
return _el;
};
Die Komponente läuft nur einmal. Danach aktualisiert nur der createEffect-Aufruf den Textknoten — direkt im DOM, ohne Re-Render.
createSignal: Der Einstieg
Das grundlegendste Primitive in SolidJS ist createSignal. Es gibt ein Tupel aus Getter und Setter zurück — ähnlich wie Reacts useState, aber mit einem entscheidenden Unterschied: Der Getter ist eine Funktion, die aufgerufen werden muss.
import { createSignal, createEffect } from "solid-js";
// Typisiertes Signal
const [name, setName] = createSignal<string>("SpockyMagicAI");
const [count, setCount] = createSignal(0);
// Getter = Funktion aufrufen!
console.log(name()); // "SpockyMagicAI"
console.log(count()); // 0
// Setter mit Wert
setCount(42);
// Setter mit Updater-Funktion (wie React)
setCount(prev => prev + 1);
// Signal-Optionen: equals verhindert unnötige Updates
const [pos, setPos] = createSignal(
{ x: 0, y: 0 },
{ equals: (a, b) => a.x === b.x && a.y === b.y }
);
Claude Code Tipp
Wenn du Claude Code anforderst, eine SolidJS-Komponente zu schreiben, sage explizit "SolidJS, kein React" — Claude kennt den Unterschied zwischen den Gettern und behandelt Signals korrekt als aufrufbare Funktionen, nicht als direkte Werte.
Warum kein Virtual DOM schneller ist
Das Virtual DOM war eine geniale Idee für 2013 — heute ist es ein notwendiges Übel. Jedes State-Update erzeugt einen neuen Virtual-DOM-Baum, der mit dem alten verglichen werden muss. Bei großen Komponentenbäumen ist dieser Diff teuer. SolidJS umgeht dieses Problem vollständig: Jede reaktive Abhängigkeit ist zur Compile-Zeit bekannt und wird direkt verdrahtet.
2. Reaktivitäts-Primitives: createMemo, createEffect, createResource
SolidJS bietet eine kohärente Sammlung von Primitiven für verschiedene reaktive Szenarien. Im Gegensatz zu React Hooks gibt es keine "Rules of Hooks" — Primitives können überall aufgerufen werden, solange sie sich in einem reaktiven Scope befinden.
createMemo: Berechnete Werte
import { createSignal, createMemo } from "solid-js";
const [items, setItems] = createSignal<number[]>([1, 2, 3, 4, 5]);
const [filter, setFilter] = createSignal("even");
// createMemo cached das Ergebnis — wird nur neu berechnet wenn
// items() oder filter() sich ändern
const filteredItems = createMemo(() => {
const list = items();
return filter() === "even"
? list.filter(n => n % 2 === 0)
: list.filter(n => n % 2 !== 0);
});
// Memo mit Gleichheitsprüfung (verhindert Re-Renders bei identischem Ergebnis)
const sum = createMemo(
() => items().reduce((a, b) => a + b, 0),
0, // initialValue
{ equals: (a, b) => a === b }
);
// filteredItems() und sum() verhalten sich wie Signals — aufrufbar als Funktion
console.log(filteredItems()); // [2, 4]
console.log(sum()); // 15
createEffect: Seiteneffekte
import { createSignal, createEffect, on, onCleanup } from "solid-js";
const [userId, setUserId] = createSignal(1);
// Einfacher Effect — tracked automatisch alle Signals die aufgerufen werden
createEffect(() => {
console.log("User ID geändert:", userId());
});
// Effect mit Cleanup (z.B. für Event Listener)
createEffect(() => {
const handler = (e: KeyboardEvent) => {
if (e.key === "Escape") setUserId(1);
};
document.addEventListener("keydown", handler);
onCleanup(() => document.removeEventListener("keydown", handler));
});
// on() für explizite Dependency-Tracking (wie useEffect deps-Array)
createEffect(on(userId, (id, prevId) => {
console.log(`User wechselte von ${prevId} zu ${id}`);
}, { defer: true })); // defer: nicht beim ersten Mount
createResource: Async-Daten
import { createSignal, createResource } from "solid-js";
type User = { id: number; name: string; email: string };
const fetchUser = async (id: number): Promise<User> => {
const res = await fetch(`https://api.example.com/users/${id}`);
if (!res.ok) throw new Error("Fetch fehlgeschlagen");
return res.json();
};
const [userId, setUserId] = createSignal(1);
// createResource(source, fetcher)
// source = reaktive Abhängigkeit; fetcher wird neu aufgerufen wenn source sich ändert
const [user, { refetch, mutate }] = createResource(userId, fetchUser);
// Im Template:
const UserCard = () => (
<div>
<Show when={!user.loading} fallback={<Spinner />}>
<Show when={!user.error} fallback={<ErrorMsg msg={user.error.message} />}>
<p>{user().name}</p>
<p>{user().email}</p>
</Show>
</Show>
<button onClick={refetch}>Neu laden</button>
</div>
);
Fortgeschritten
batch und untrack
batch() gruppiert mehrere State-Updates zu einem einzigen reaktiven Durchlauf. untrack() liest ein Signal, ohne eine Abhängigkeit zu registrieren.
import { batch, untrack } from "solid-js";
// Ohne batch: 3 separate Updates → 3 Effect-Durchläufe
// Mit batch: 1 Update → 1 Effect-Durchlauf
batch(() => {
setFirstName("Maximilian");
setLastName("Müller");
setAge(32);
});
// untrack: Signal lesen ohne Abhängigkeit zu registrieren
createEffect(() => {
const currentCount = count(); // registriert Abhängigkeit
const unchangedBase = untrack(baseValue); // KEIN Tracking
console.log(currentCount - unchangedBase);
});
3. Stores & Mutation: createStore, produce, reconcile
Für komplexe, verschachtelte State-Strukturen bietet SolidJS createStore — ein reaktiver Proxy, der granulare Updates auf tief verschachtelte Objekte ermöglicht, ohne den gesamten State zu ersetzen.
Store
createStore Grundlagen
import { createStore, produce, reconcile, unwrap } from "solid-js/store";
interface TodoItem {
id: number;
text: string;
done: boolean;
tags: string[];
}
interface AppState {
todos: TodoItem[];
filter: "all" | "active" | "done";
user: { name: string; theme: "light" | "dark" };
}
const [state, setState] = createStore<AppState>({
todos: [],
filter: "all",
user: { name: "Daniel", theme: "dark" },
});
// Direkter Pfad-Update (SolidJS-spezifisch)
setState("user", "theme", "light");
setState("filter", "active");
// Array-Update per Index
setState("todos", 0, "done", true);
// Array-Update per Filterfunktion
setState("todos", (todo) => !todo.done, "tags", (tags) => [...tags, "wichtig"]);
// state lesen — kein () nötig (Proxy-Objekt, kein Signal)
console.log(state.user.name); // "Daniel"
console.log(state.todos.length);
produce: Immer-ähnliche Mutations
import { produce } from "solid-js/store";
// produce() erlaubt direkte Mutations (wie Immer.js)
setState(produce((s) => {
s.todos.push({
id: Date.now(),
text: "Claude Code konfigurieren",
done: false,
tags: ["dev"],
});
s.todos
.filter((t) => t.done)
.forEach((t) => (t.tags = []));
}));
reconcile: Externe Daten synchronisieren
import { reconcile } from "solid-js/store";
// reconcile() diffed externe Daten gegen aktuellen Store
// Ideal für API-Responses — verhindert unnötige DOM-Updates
async function refreshTodos() {
const fresh = await fetchTodosFromApi();
setState("todos", reconcile(fresh, { key: "id", merge: true }));
// Nur wirklich geänderte Todos triggern DOM-Updates
}
Store vs. Signal — wann was?
Verwende Signal für einfache skalare Werte (Zahlen, Strings, Booleans) und Store für verschachtelte Objekte und Arrays. Stores sind Proxies — du kannst sie nicht destrukturieren ohne den reaktiven Kontext zu verlieren. Immer state.todo.text schreiben, nie const {todo} = state.
4. Control-Flow Komponenten
SolidJS bietet eingebaute Komponenten für reaktive Verzweigungen und Listen. Sie sind essentiell für Performance, weil sie granulare DOM-Updates ermöglichen — kein unnötiges Re-Mounting bei jeder Änderung.
<Show>: Bedingtes Rendering
import { Show } from "solid-js";
// when: beliebiger Wert (wird truthy geprüft)
// fallback: optional, was gezeigt wird wenn when falsy
// keyed: wenn true, unmountet Kinder bei jeder when-Änderung (Default: false)
const UserView = () => (
<Show
when={isLoggedIn()}
fallback={<LoginForm />}
keyed
>
{(user) => <Dashboard user={user} />}
</Show>
);
<For>: Listen-Rendering
import { For, Index } from "solid-js";
// For: Item-basiert — behält DOM-Knoten wenn sich Reihenfolge ändert
// Ideal für Arrays von Objekten (referenzielle Gleichheit)
const TodoList = () => (
<ul>
<For each={state.todos} fallback={<li>Keine Todos vorhanden.</li>}>
{(todo, index) => (
<li>
<input
type="checkbox"
checked={todo.done}
onChange={() => setState("todos", index(), "done", (d) => !d)}
/>
<span>{todo.text}</span>
<For each={todo.tags}>
{(tag) => <span class="tag">{tag}</span>}
</For>
</li>
)}
</For>
</ul>
);
// Index: Position-basiert — Ideal für primitive Arrays (Strings, Zahlen)
const NumberList = () => (
<Index each={[1, 2, 3]}>
{(item, i) => <span>{i}: {item()}</span>}
</Index>
);
<Switch>/<Match>, <Dynamic>, <Portal>, <ErrorBoundary>
import { Switch, Match, Dynamic, Portal, ErrorBoundary } from "solid-js";
// Switch + Match: Multi-Branch
const StatusView = () => (
<Switch fallback={<UnknownStatus />}>
<Match when={status() === "loading"}><Spinner /></Match>
<Match when={status() === "error"}><ErrorBanner /></Match>
<Match when={status() === "success"}><DataView /></Match>
</Switch>
);
// Dynamic: Komponente zur Laufzeit wählen
const componentMap = { card: CardView, list: ListView, grid: GridView };
const DynamicView = () => (
<Dynamic component={componentMap[viewMode()]} data={items()} />
);
// Portal: DOM außerhalb des Komponenten-Baums
const Modal = () => (
<Portal mount={document.getElementById("modal-root")!}>
<div class="modal-overlay">...</div>
</Portal>
);
// ErrorBoundary: Fehlerbehandlung
const SafeApp = () => (
<ErrorBoundary
fallback={(err, reset) => (
<div>
<p>Fehler: {err.message}</p>
<button onClick={reset}>Neu versuchen</button>
</div>
)}
>
<ChildThatMightThrow />
</ErrorBoundary>
);
Claude Code Tipp
Frag Claude Code: "Erstelle eine SolidJS-Komponente mit <For> die eine Liste von Produkten rendert und <Show> für den Ladezustand nutzt." Claude generiert korrekte Solid-Syntax inklusive Getter-Aufrufen und prop-drilling-freier Stores.
5. SolidStart: File-based Routing & Server Functions
SolidStart
Was ist SolidStart?
SolidStart ist das offizielle Full-Stack-Framework für SolidJS — vergleichbar mit Next.js für React oder Nuxt für Vue. Es bietet file-based Routing, Server Functions, SSR/SSG und Streaming.
File-based Routing
// Projektstruktur:
src/
routes/
index.tsx → /
about.tsx → /about
blog/
index.tsx → /blog
[slug].tsx → /blog/:slug (dynamische Route)
[...rest].tsx → /blog/* (Catch-all)
(auth)/ → Layout-Gruppe (kein URL-Segment)
login.tsx → /login
register.tsx → /register
app.tsx → Root Layout
// src/routes/blog/[slug].tsx
import { createAsync, useParams } from "@solidjs/router";
import { getPost } from "~/lib/posts"; // Server Function
// createAsync: reaktive Async-Daten (kombiniert createResource mit Routing)
export default function BlogPost() {
const params = useParams();
const post = createAsync(() => getPost(params.slug));
return (
<Show when={post()} fallback={<PostSkeleton />}>
{(p) => (
<article>
<h1>{p.title}</h1>
<div innerHTML={p.html} />
</article>
)}
</Show>
);
}
Server Functions: "use server"
// src/lib/posts.ts
"use server"; // Diese Datei läuft NUR auf dem Server
import { db } from "~/lib/db";
import { getRequestEvent } from "solid-js/web";
export async function getPost(slug: string) {
// Direkter DB-Zugriff — kein API-Layer nötig
const post = await db.post.findFirst({ where: { slug } });
if (!post) throw new Error("Post nicht gefunden");
return post;
}
export async function createPost(data: FormData) {
"use server"; // Kann auch inline in einer Funktion stehen
const event = getRequestEvent(); // Zugriff auf Request/Response
const session = await getSession(event);
if (!session.userId) throw new Error("Nicht eingeloggt");
return db.post.create({
data: {
title: data.get("title") as string,
content: data.get("content") as string,
authorId: session.userId,
},
});
}
Middleware & Cache
// src/middleware.ts
import { createMiddleware } from "@solidjs/start/middleware";
export default createMiddleware({
onRequest: [
async ({ request, forward }) => {
// Auth-Header prüfen
const token = request.headers.get("Authorization");
if (!token) return new Response("Unauthorized", { status: 401 });
return forward();
},
],
});
// Cache für Server Functions
import { cache } from "@solidjs/router";
const getCachedPost = cache(getPost, "post"); // Cache-Key "post"
// Automatisch dedupliziert bei gleichzeitigen Requests
// Cache wird geleert bei server-seitigen Mutations (action())
Performance
Wie funktioniert Fine-grained Reaktivität?
Wenn du in React setState aufrufst, wird die gesamte Komponente (und standardmäßig alle Kind-Komponenten) neu gerendert. SolidJS erstellt stattdessen beim ersten Render einen Dependency-Graph: Jeder reaktive Ausdruck (Effect, Memo, DOM-Binding) weiß exakt, von welchen Signals er abhängt. Änderungen propagieren nur entlang dieser Kanten.
React Re-render vs. Solid Fine-grained Update
// React: Gesamte Komponente rendert neu
function ReactCounter() {
const [count, setCount] = useState(0);
console.log("Render!"); // wird bei JEDEM setCount aufgerufen
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(c => c + 1)}>+</button>
<ExpensiveChild /> // rendert AUCH neu (außer React.memo)
</div>
);
}
// SolidJS: Nur der <p>-Textknoten ändert sich
function SolidCounter() {
const [count, setCount] = createSignal(0);
console.log("Render!"); // wird NUR EINMAL beim ersten Mount aufgerufen
return (
<div>
<p>{count()}</p> {/* nur dieser DOM-Knoten updatet */}
<button onClick={() => setCount(c => c + 1)}>+</button>
<ExpensiveChild /> // NIEMALS neu gemounted, kein React.memo nötig
</div>
);
}
Benchmark-Vergleich (js-framework-benchmark, 2026)
| Framework |
Create 1k rows (ms) |
Update 1k rows (ms) |
Select row (ms) |
Memory (MB) |
| SolidJS 1.9 |
41.2 |
28.7 |
5.1 |
3.2 |
| Svelte 5 |
44.8 |
31.2 |
5.8 |
3.9 |
| Vue 3.5 |
52.1 |
38.4 |
6.9 |
5.1 |
| React 19 |
68.3 |
58.9 |
9.2 |
7.4 |
| Angular 18 |
74.1 |
62.3 |
10.4 |
8.8 |
Benchmarks richtig lesen
Diese Zahlen sind synthetische Benchmarks — in realen Applikationen ist der Unterschied kleiner. Entscheidend ist: SolidJS ist konsistent schneller als React, besonders bei häufigen, granularen Updates (Echtzeit-Daten, Live-Charts, kollaborative Tools).
Wann SolidJS wählen?
| Szenario |
SolidJS |
React |
| Echtzeit-Dashboards, Live-Daten |
Ideal |
Möglich (mit Optimierungen) |
| Performance-kritische SPAs |
Ideal |
Gut mit React.memo/useMemo |
| Großes Team, viel Erfahrung |
Lernkurve |
Breites Ökosystem |
| Full-Stack mit SSR/SSG |
SolidStart |
Next.js |
| Mobile (React Native) |
Kein Äquivalent |
React Native |
| Greenfield-Projekt, 2026 |
Sehr empfehlenswert |
Bewährt, sicher |
| Migration bestehende React-App |
Hoher Aufwand |
Status quo |
Claude Code & SolidJS — Workflow-Empfehlung
Claude Code versteht SolidJS nativ. Nutze es für: Komponenten-Scaffolding ("erstelle einen SolidStart API-Route Handler"), Store-Design ("entwirf einen createStore für diesen Datentyp") und Performance-Analyse ("erkläre warum dieser Effect unnötig triggert"). Die KI erkennt typische Solid-Antipatterns wie das Destrukturieren von Store-Objekten oder das vergessene () beim Getter.
SolidJS
TypeScript
Fine-grained Reactivity
SolidStart
createSignal
createStore
Frontend 2026
Claude Code
JavaScript Frameworks
Virtual DOM
SolidJS mit KI-Unterstützung entwickeln
Starte deinen kostenlosen Trial und lass Claude Code deine SolidJS-Projekte beschleunigen — von der Komponentenstruktur bis zum SolidStart-Deployment.
Jetzt kostenlos testen →