[TERMINAL · SKILLS]
> mounting /skills...
> indexing skill manifests...
> linking agents: claude · codex · gemini · cursor
> ready.
[░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0%
Use Cases/Build a Full-Stack App with SolidStart

Build a Full-Stack App with SolidStart

Build a performant full-stack web app with SolidStart — fine-grained reactivity, server functions, streaming SSR, and zero virtual DOM for a fast-by-default experience.

Development#solidjs#reactive#signals#performance#frontend
Works with:claude-codeopenai-codexgemini-clicursor

Skills stack · 4 skills

Avg quality 93/100·All SAFE
>

solid-js

v1.1.0

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.

100/100 quality
1.81× impact
SAFE· stale score
View skill
>

drizzle-orm

v1.1.0

Drizzle ORM is a lightweight, type-safe TypeScript ORM whose query builder maps directly to SQL, with a companion CLI (drizzle-kit) that generates and applies migrations. Use when a user asks to define a database schema in TypeScript, write typed Postgres, MySQL or SQLite queries, run drizzle-kit generate, migrate or push, or connect Drizzle to Neon, Turso, Cloudflare D1, PGlite or node-postgres.

87/100 quality
6.70× impact
SAFE· stale score
View skill
>

tailwindcss

v1.1.0

Tailwind CSS is a utility-first CSS framework: you style pages with small classes in the markup and ship only the CSS you use. Covers Tailwind v4 (CSS-first config with @theme, @import "tailwindcss", Vite and PostCSS plugins), responsive layouts, dark mode, custom animations, and the v3 to v4 renames. Use when someone asks to "style with Tailwind", "set up Tailwind v4", "upgrade Tailwind to v4", "add dark mode", or "fix Tailwind classes that stopped working".

93/100 quality
2.28× impact
SAFE· stale score
View skill
>

zod

v1.1.0

Zod is a TypeScript-first schema library that validates data at runtime and infers static types from the same schema. Use when validating API request bodies, form input, environment variables or config files, parsing untrusted JSON, or migrating from Zod 3 to Zod 4. Covers z.object, safeParse, z.infer, coercion, discriminated unions, refinements, JSON Schema output and the v4 API changes.

93/100 quality
3.87× impact
SAFE· stale score
View skill
$

Eli is frustrated with React's re-rendering performance. His dashboard with 500 rows of real-time data causes janky updates because React re-renders entire component trees. SolidJS uses fine-grained reactivity — only the exact DOM node that changed updates, nothing else. SolidStart adds server functions, SSR, and file-based routing. Same mental model as React, but fundamentally faster.

Step 1: Project Setup

bash
npm init solid@latest my-app -- --template basic --server
cd my-app
npm install @solidjs/router drizzle-orm @libsql/client zod
npm install -D drizzle-kit

Step 2: Server Functions (RPC-style)

typescript
// src/lib/server/db.ts
"use server";
import { drizzle } from "drizzle-orm/libsql";
import { createClient } from "@libsql/client";
import * as schema from "./schema";

const client = createClient({ url: process.env.DATABASE_URL!, authToken: process.env.DB_AUTH_TOKEN });
export const db = drizzle(client, { schema });
typescript
// src/lib/server/tasks.ts
"use server";
import { db } from "./db";
import { tasks } from "./schema";
import { eq, desc } from "drizzle-orm";
import { z } from "zod";

const CreateTaskSchema = z.object({
  title: z.string().min(1).max(200),
  priority: z.enum(["low", "medium", "high"]),
});

export async function getTasks() {
  return db.query.tasks.findMany({
    orderBy: [desc(tasks.createdAt)],
    limit: 100,
  });
}

export async function createTask(input: unknown) {
  const parsed = CreateTaskSchema.parse(input);
  const [task] = await db.insert(tasks).values({
    ...parsed,
    id: crypto.randomUUID(),
    completed: false,
    createdAt: new Date(),
  }).returning();
  return task;
}

export async function toggleTask(id: string) {
  const task = await db.query.tasks.findFirst({ where: eq(tasks.id, id) });
  if (!task) throw new Error("Task not found");
  await db.update(tasks).set({ completed: !task.completed }).where(eq(tasks.id, id));
}

export async function deleteTask(id: string) {
  await db.delete(tasks).where(eq(tasks.id, id));
}

Step 3: Reactive UI with Fine-Grained Updates

tsx
// src/routes/index.tsx
import { createSignal, For, Show, createResource, Suspense } from "solid-js";
import { createAsync, useAction, useSubmission } from "@solidjs/router";
import { getTasks, createTask, toggleTask, deleteTask } from "~/lib/server/tasks";

export default function Home() {
  const tasks = createAsync(() => getTasks());
  const [newTitle, setNewTitle] = createSignal("");
  const [priority, setPriority] = createSignal<"low" | "medium" | "high">("medium");

  const addAction = useAction(createTask);
  const adding = useSubmission(createTask);

  const handleSubmit = async (e: Event) => {
    e.preventDefault();
    if (!newTitle().trim()) return;
    await addAction({ title: newTitle(), priority: priority() });
    setNewTitle("");
  };

  return (
    <main class="max-w-2xl mx-auto p-6">
      <h1 class="text-3xl font-bold mb-6">Tasks</h1>

      <form onSubmit={handleSubmit} class="flex gap-2 mb-6">
        <input
          value={newTitle()}
          onInput={(e) => setNewTitle(e.currentTarget.value)}
          placeholder="New task..."
          class="flex-1 px-3 py-2 border rounded"
        />
        <select
          value={priority()}
          onChange={(e) => setPriority(e.currentTarget.value as any)}
          class="px-3 py-2 border rounded"
        >
          <option value="low">Low</option>
          <option value="medium">Medium</option>
          <option value="high">High</option>
        </select>
        <button
          type="submit"
          disabled={adding.pending}
          class="px-4 py-2 bg-blue-600 text-white rounded disabled:opacity-50"
        >
          {adding.pending ? "Adding..." : "Add"}
        </button>
      </form>

      <Suspense fallback={<div class="animate-pulse">Loading tasks...</div>}>
        <TaskList tasks={tasks() || []} />
      </Suspense>
    </main>
  );
}

function TaskList(props: { tasks: any[] }) {
  return (
    <div class="space-y-2">
      <For each={props.tasks}>
        {(task) => <TaskRow task={task} />}
      </For>
      <Show when={props.tasks.length === 0}>
        <p class="text-gray-500 text-center py-8">No tasks yet. Add one above!</p>
      </Show>
    </div>
  );
}

function TaskRow(props: { task: any }) {
  const toggle = useAction(toggleTask);
  const remove = useAction(deleteTask);

  const priorityColor = () => ({
    high: "bg-red-100 text-red-700",
    medium: "bg-yellow-100 text-yellow-700",
    low: "bg-green-100 text-green-700",
  }[props.task.priority]);

  return (
    <div class="flex items-center gap-3 p-3 bg-white rounded border hover:shadow-sm transition-shadow">
      <input
        type="checkbox"
        checked={props.task.completed}
        onChange={() => toggle(props.task.id)}
        class="w-5 h-5 rounded"
      />
      <span
        class="flex-1"
        classList={{ "line-through text-gray-400": props.task.completed }}
      >
        {props.task.title}
      </span>
      <span class={`text-xs px-2 py-0.5 rounded ${priorityColor()}`}>
        {props.task.priority}
      </span>
      <button onClick={() => remove(props.task.id)} class="text-red-400 hover:text-red-600">
        ✕
      </button>
    </div>
  );
}

Step 4: Streaming SSR with Error Boundaries

tsx
// src/routes/dashboard.tsx
import { Suspense, ErrorBoundary } from "solid-js";
import { createAsync } from "@solidjs/router";

export default function Dashboard() {
  // These load in parallel, streaming to the client as they resolve
  const stats = createAsync(() => getStats());
  const recentActivity = createAsync(() => getRecentActivity());
  const teamMembers = createAsync(() => getTeamMembers());

  return (
    <div class="grid grid-cols-3 gap-6 p-6">
      <ErrorBoundary fallback={(err) => <div class="text-red-500">Failed to load stats</div>}>
        <Suspense fallback={<StatsSkeleton />}>
          <StatsPanel stats={stats()} />
        </Suspense>
      </ErrorBoundary>

      <ErrorBoundary fallback={(err) => <div class="text-red-500">Failed to load activity</div>}>
        <Suspense fallback={<ActivitySkeleton />}>
          <ActivityFeed items={recentActivity()} />
        </Suspense>
      </ErrorBoundary>

      <ErrorBoundary fallback={(err) => <div class="text-red-500">Failed to load team</div>}>
        <Suspense fallback={<TeamSkeleton />}>
          <TeamPanel members={teamMembers()} />
        </Suspense>
      </ErrorBoundary>
    </div>
  );
}

Summary

Eli's dashboard renders 500 rows with zero jank. When a single cell updates, only that DOM text node changes — not the row, not the table, not the component. SolidStart's server functions give him type-safe RPC to the database without writing API routes. Streaming SSR sends the page shell immediately while data loads in parallel — each panel appears as its data resolves. The bundle is 30% smaller than the React equivalent because there's no virtual DOM runtime. Solid's createSignal and For components are familiar to React developers but fundamentally more efficient.

Related use cases