DEV Community

Alex Spinov
Alex Spinov

Posted on

SolidStart Has a Free Framework: Server-Side Rendering with Fine-Grained Reactivity

Why SolidStart?

SolidStart is the meta-framework for SolidJS - the UI library with fine-grained reactivity that skips the virtual DOM entirely. If you want React-like DX with significantly better runtime performance, SolidStart gives you SSR, routing, and server functions out of the box.

Quick Start

npm init solid@latest my-app
cd my-app
npm install
npm run dev
Enter fullscreen mode Exit fullscreen mode

Server Functions (RPC)

// src/lib/api.ts
'use server';

import { db } from './db';

export async function getPosts(query?: string) {
  return db.posts.findMany({
    where: query ? { title: { contains: query } } : undefined,
    orderBy: { createdAt: 'desc' },
    take: 20,
  });
}

export async function createPost(title: string, body: string) {
  if (!title || !body) throw new Error('Title and body required');
  return db.posts.create({ data: { title, body } });
}

export async function deletePost(id: string) {
  return db.posts.delete({ where: { id } });
}
Enter fullscreen mode Exit fullscreen mode

Routes with Data Loading

// src/routes/posts/index.tsx
import { createAsync, useSearchParams } from '@solidjs/router';
import { For, Suspense } from 'solid-js';
import { getPosts } from '~/lib/api';

export default function PostsPage() {
  const [params] = useSearchParams();
  const posts = createAsync(() => getPosts(params.q));

  return (
    <div>
      <h1>Posts</h1>
      <form method="get">
        <input name="q" value={params.q || ''} placeholder="Search..." />
      </form>
      <Suspense fallback={<p>Loading...</p>}>
        <ul>
          <For each={posts()}>
            {(post) => (
              <li>
                <a href={`/posts/${post.id}`}>{post.title}</a>
              </li>
            )}
          </For>
        </ul>
      </Suspense>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Fine-Grained Reactivity

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

export default function Counter() {
  const [count, setCount] = createSignal(0);
  const doubled = createMemo(() => count() * 2);

  createEffect(() => {
    console.log('Count changed:', count());
  });

  return (
    <div>
      <p>Count: {count()}</p>
      <p>Doubled: {doubled()}</p>
      <button onClick={() => setCount(c => c + 1)}>Increment</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

API Routes

// src/routes/api/posts.ts
import { json } from '@solidjs/router';
import type { APIEvent } from '@solidjs/start/server';
import { db } from '~/lib/db';

export async function GET(event: APIEvent) {
  const posts = await db.posts.findMany({ take: 50 });
  return json(posts);
}

export async function POST(event: APIEvent) {
  const body = await event.request.json();
  const post = await db.posts.create({ data: body });
  return json(post, { status: 201 });
}
Enter fullscreen mode Exit fullscreen mode

Middleware

// src/middleware.ts
import { createMiddleware } from '@solidjs/start/middleware';

export default createMiddleware({
  onRequest: [
    (event) => {
      const start = Date.now();
      event.locals.startTime = start;
    },
  ],
  onBeforeResponse: [
    (event) => {
      const duration = Date.now() - event.locals.startTime;
      console.log(`${event.request.method} ${event.request.url} - ${duration}ms`);
    },
  ],
});
Enter fullscreen mode Exit fullscreen mode

Real-World Use Case

A fintech startup needed a dashboard that updates hundreds of data points in real-time. React re-rendered entire component trees on each WebSocket message. SolidStart with fine-grained reactivity only updates the exact DOM nodes that changed. Result: 60fps even with 500+ live-updating cells, and bundle size dropped 40%.


Building reactive apps? I create custom data pipelines and automation tools. Check out my web scraping toolkit on Apify or reach me at spinov001@gmail.com for custom solutions.

Top comments (0)