Skip to content

Fix: SolidStart Not Working — Routes Not Rendering, Server Functions Failing, or Hydration Errors

FixDevs ·

Quick Answer

How to fix SolidStart issues — file-based routing, server functions, createAsync data loading, middleware, sessions, and deployment configuration.

The Problem

A SolidStart route renders blank:

GET /about → blank page, no content

Or a server function throws:

'use server';

async function getUsers() {
  return db.query.users.findMany();
}
// Error: db is not defined — or — 'use server' is not recognized

Or the page hydrates incorrectly:

Hydration mismatch: server rendered "Hello" but client rendered ""

Why This Happens

SolidStart is SolidJS’s meta-framework, similar to Next.js but for Solid. It uses Vinxi under the hood:

  • SolidStart uses file-based routing in src/routes/ — files and directories map to URL paths. A missing or misplaced file means no route.
  • Server functions need 'use server' directive — code marked with 'use server' runs only on the server. Without the directive, server-only code (database, fs) is bundled into the client and crashes.
  • SolidJS reactivity is different from React — Solid uses signals and fine-grained reactivity. Destructuring props or accessing signals outside of JSX/effects breaks reactivity.
  • Hydration requires matching server/client output — if the server renders different HTML than the client expects (e.g., browser-only code runs during SSR), hydration fails.

Fix 1: Project Setup

npm init solid@latest my-app
# Select: SolidStart, TypeScript
cd my-app && npm install && npm run dev
// app.config.ts — SolidStart configuration
import { defineConfig } from '@solidjs/start/config';

export default defineConfig({
  server: {
    preset: 'node-server',  // 'vercel' | 'netlify' | 'cloudflare-pages'
  },
  vite: {
    // Vite plugins and config
  },
});
src/routes/
├── index.tsx              # /
├── about.tsx              # /about
├── [...404].tsx           # Catch-all 404
├── posts/
│   ├── index.tsx          # /posts
│   └── [id].tsx           # /posts/:id
├── (auth)/
│   ├── login.tsx          # /login (group doesn't add URL segment)
│   └── register.tsx       # /register
└── api/
    ├── users.ts           # /api/users (API route)
    └── posts/
        └── [id].ts        # /api/posts/:id

Fix 2: Routes and Data Loading

// src/routes/index.tsx — home page
import { A } from '@solidjs/router';

export default function Home() {
  return (
    <main>
      <h1>Welcome to SolidStart</h1>
      <A href="/posts">View Posts</A>
    </main>
  );
}
// src/routes/posts/index.tsx — list with server data
import { createAsync, query } from '@solidjs/router';
import { For, Suspense } from 'solid-js';

// Define a query — cached and deduplicated
const getPosts = query(async () => {
  'use server';
  return db.query.posts.findMany({
    where: eq(posts.published, true),
    orderBy: desc(posts.createdAt),
  });
}, 'posts');

// Route data — preloaded before component renders
export const route = {
  preload: () => getPosts(),
};

export default function PostsPage() {
  const posts = createAsync(() => getPosts());

  return (
    <main>
      <h1>Blog Posts</h1>
      <Suspense fallback={<p>Loading posts...</p>}>
        <For each={posts()}>
          {(post) => (
            <article>
              <A href={`/posts/${post.id}`}>
                <h2>{post.title}</h2>
              </A>
              <p>{post.excerpt}</p>
            </article>
          )}
        </For>
      </Suspense>
    </main>
  );
}
// src/routes/posts/[id].tsx — dynamic route
import { createAsync, query, useParams } from '@solidjs/router';
import { Show, Suspense } from 'solid-js';

const getPost = query(async (id: string) => {
  'use server';
  const post = await db.query.posts.findFirst({
    where: eq(posts.id, id),
  });
  if (!post) throw new Error('Post not found');
  return post;
}, 'post');

export const route = {
  preload: ({ params }: { params: { id: string } }) => getPost(params.id),
};

export default function PostPage() {
  const params = useParams();
  const post = createAsync(() => getPost(params.id));

  return (
    <Suspense fallback={<p>Loading...</p>}>
      <Show when={post()} fallback={<p>Post not found</p>}>
        {(p) => (
          <article>
            <h1>{p().title}</h1>
            <div innerHTML={p().htmlContent} />
          </article>
        )}
      </Show>
    </Suspense>
  );
}

Fix 3: Server Functions and Actions

// src/lib/server.ts — server functions
'use server';

import { db } from './db';

export async function getUsers() {
  return db.query.users.findMany();
}

export async function createUser(name: string, email: string) {
  const [user] = await db.insert(users).values({ name, email }).returning();
  return user;
}

export async function deleteUser(id: string) {
  await db.delete(users).where(eq(users.id, id));
}
// src/routes/users.tsx — using server functions with actions
import { createAsync, query, action, useAction, useSubmission } from '@solidjs/router';
import { createUser, deleteUser, getUsers } from '~/lib/server';

const getUsersQuery = query(async () => {
  'use server';
  return getUsers();
}, 'users');

// Define actions for mutations
const createUserAction = action(async (formData: FormData) => {
  'use server';
  const name = formData.get('name') as string;
  const email = formData.get('email') as string;
  await createUser(name, email);
});

const deleteUserAction = action(async (id: string) => {
  'use server';
  await deleteUser(id);
});

export default function UsersPage() {
  const users = createAsync(() => getUsersQuery());
  const submission = useSubmission(createUserAction);

  return (
    <div>
      <h1>Users</h1>

      {/* Form with action — progressive enhancement */}
      <form action={createUserAction} method="post">
        <input name="name" placeholder="Name" required />
        <input name="email" type="email" placeholder="Email" required />
        <button type="submit" disabled={submission.pending}>
          {submission.pending ? 'Adding...' : 'Add User'}
        </button>
      </form>

      <Suspense fallback={<p>Loading...</p>}>
        <For each={users()}>
          {(user) => (
            <div>
              <span>{user.name} ({user.email})</span>
              <button onClick={() => {
                const del = useAction(deleteUserAction);
                del(user.id);
              }}>
                Delete
              </button>
            </div>
          )}
        </For>
      </Suspense>
    </div>
  );
}

Fix 4: Layouts

// src/routes/(app).tsx — layout for authenticated routes
import { A, Outlet } from '@solidjs/router';

export default function AppLayout() {
  return (
    <div class="flex min-h-screen">
      <nav class="w-64 bg-gray-900 text-white p-4">
        <h2 class="text-xl font-bold mb-4">My App</h2>
        <ul class="space-y-2">
          <li><A href="/" class="hover:text-blue-400">Home</A></li>
          <li><A href="/posts" class="hover:text-blue-400">Posts</A></li>
          <li><A href="/settings" class="hover:text-blue-400">Settings</A></li>
        </ul>
      </nav>
      <main class="flex-1 p-8">
        <Outlet />
      </main>
    </div>
  );
}

// src/app.tsx — root component
import { Router } from '@solidjs/router';
import { FileRoutes } from '@solidjs/start/router';
import { Suspense } from 'solid-js';
import './app.css';

export default function App() {
  return (
    <Router root={(props) => (
      <Suspense>{props.children}</Suspense>
    )}>
      <FileRoutes />
    </Router>
  );
}

Fix 5: API Routes

// src/routes/api/users.ts — REST API endpoint
import { json } from '@solidjs/router';
import type { APIEvent } from '@solidjs/start/server';

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

export async function POST(event: APIEvent) {
  const body = await event.request.json();
  const [user] = await db.insert(users).values(body).returning();
  return json(user, { status: 201 });
}

// src/routes/api/users/[id].ts
export async function GET(event: APIEvent) {
  const id = event.params.id;
  const user = await db.query.users.findFirst({ where: eq(users.id, id) });
  if (!user) return json({ error: 'Not found' }, { status: 404 });
  return json(user);
}

export async function DELETE(event: APIEvent) {
  const id = event.params.id;
  await db.delete(users).where(eq(users.id, id));
  return new Response(null, { status: 204 });
}

Fix 6: Middleware and Sessions

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

export default createMiddleware({
  onRequest: [
    // Auth middleware
    async (event) => {
      const token = event.request.headers.get('authorization')?.replace('Bearer ', '');

      if (token) {
        try {
          const user = await verifyToken(token);
          event.locals.user = user;
        } catch {
          // Invalid token — continue without user
        }
      }
    },
    // Logging middleware
    async (event) => {
      console.log(`${event.request.method} ${event.request.url}`);
    },
  ],
});

// Access in server functions
'use server';
import { getRequestEvent } from 'solid-js/web';

export async function getCurrentUser() {
  const event = getRequestEvent();
  return event?.locals.user ?? null;
}

Still Not Working?

Blank page — check that the route file is in src/routes/ and exports a default component. Also verify src/app.tsx includes <FileRoutes /> inside a <Router>.

“use server” not recognized — ensure the string 'use server' is at the very top of the function or file. It must be a string literal, not a variable. Also check that the SolidStart Vite plugin is configured in app.config.ts.

Reactivity doesn’t work — SolidJS signals must be called as functions in JSX: {count()} not {count}. Don’t destructure props: use props.name instead of const { name } = props. Don’t access signals outside of JSX or createEffect.

Hydration mismatch — code that uses window, document, or localStorage runs on the server during SSR. Guard with import { isServer } from 'solid-js/web'; if (!isServer) { ... } or use onMount() for client-only effects.

For related framework issues, see Fix: SolidJS Not Working and Fix: TanStack Start Not Working.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles