Documentation Index
Fetch the complete documentation index at: https://mintlify.com/TanStack/router/llms.txt
Use this file to discover all available pages before exploring further.
Solid Start
TanStack Start provides full support for SolidJS, enabling you to build type-safe, full-stack applications with Solid’s fine-grained reactivity.
Overview
Solid Start combines:
- SolidJS - Fine-grained reactive UI library
- TanStack Router - Type-safe routing with search params
- TanStack Start - Server functions and full-stack capabilities
Installation
npm install @tanstack/solid-start @tanstack/solid-router
Server Functions
Create server functions with createServerFn:
import { createServerFn } from '@tanstack/solid-start'
import { z } from 'zod'
const fetchUser = createServerFn({ method: 'GET' })
.inputValidator((id: string) => id)
.handler(async ({ data }) => {
const user = await db.user.findUnique({ where: { id: data } })
return user
})
Using in Route Loaders
import { createFileRoute } from '@tanstack/solid-router'
import { fetchUser } from '~/utils/users'
export const Route = createFileRoute('/users/$userId')({
loader: async ({ params }) => {
const user = await fetchUser({ data: params.userId })
return { user }
},
component: UserProfile,
})
function UserProfile() {
const data = Route.useLoaderData()
return <div>User: {data().user.name}</div>
}
POST Mutations
import { createServerFn } from '@tanstack/solid-start'
import { z } from 'zod'
const updateProfile = createServerFn({ method: 'POST' })
.inputValidator((data: { name: string; email: string }) => data)
.handler(async ({ data }) => {
await db.user.update({ where: { id: 1 }, data })
return { success: true }
})
useServerFn Hook
Use useServerFn to call server functions from components with automatic redirect handling:
import { useServerFn } from '@tanstack/solid-start'
import { updateProfile } from '~/utils/users'
function ProfileEditor() {
const updateProfileFn = useServerFn(updateProfile)
const [pending, setPending] = createSignal(false)
const handleSave = async () => {
setPending(true)
try {
await updateProfileFn({
data: { name: 'John', email: 'john@example.com' }
})
} finally {
setPending(false)
}
}
return (
<button onClick={handleSave} disabled={pending()}>
{pending() ? 'Saving...' : 'Save'}
</button>
)
}
Components
StartClient
The root client component for Solid applications:
// src/entry-client.tsx
import { StartClient } from '@tanstack/solid-start/client'
import { hydrate } from 'solid-js/web'
hydrate(() => <StartClient />, document.getElementById('root')!)
StartServer
The root server component for SSR:
// src/entry-server.tsx
import { StartServer } from '@tanstack/solid-start/server'
import { renderToString } from 'solid-js/web'
export async function render(request: Request) {
const router = createRouter()
// ... setup router
const html = renderToString(() => <StartServer router={router} />)
return new Response(html, {
headers: { 'Content-Type': 'text/html' },
})
}
Routing
File-Based Routes
Organize routes in the src/routes directory:
src/
routes/
__root.tsx # Root route
index.tsx # /
about.tsx # /about
posts/
index.tsx # /posts
$postId.tsx # /posts/:postId
Root Route
// src/routes/__root.tsx
import { createRootRoute } from '@tanstack/solid-router'
import { HydrationScript } from 'solid-js/web'
import { HeadContent, Scripts } from '@tanstack/solid-router'
export const Route = createRootRoute({
head: () => ({
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
],
}),
shellComponent: ({ children }) => (
<html>
<head>
<HydrationScript />
</head>
<body>
<HeadContent />
{children}
<Scripts />
</body>
</html>
),
})
Dynamic Routes
// src/routes/posts/$postId.tsx
import { createFileRoute } from '@tanstack/solid-router'
import { fetchPost } from '~/utils/posts'
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => {
const post = await fetchPost({ data: params.postId })
return { post }
},
component: PostPage,
})
function PostPage() {
const data = Route.useLoaderData()
return (
<article>
<h1>{data().post.title}</h1>
<p>{data().post.body}</p>
</article>
)
}
Reactivity
Signals in Server Functions
Solid’s signals work seamlessly with server functions:
import { createSignal } from 'solid-js'
import { useServerFn } from '@tanstack/solid-start'
import { searchUsers } from '~/utils/users'
function UserSearch() {
const [query, setQuery] = createSignal('')
const [results, setResults] = createSignal([])
const searchFn = useServerFn(searchUsers)
const handleSearch = async () => {
const data = await searchFn({ data: { query: query() } })
setResults(data)
}
return (
<div>
<input
value={query()}
onInput={(e) => setQuery(e.currentTarget.value)}
/>
<button onClick={handleSearch}>Search</button>
<For each={results()}>
{(user) => <div>{user.name}</div>}
</For>
</div>
)
}
Resources
Use createResource for async data with Suspense:
import { createResource, Suspense } from 'solid-js'
import { useServerFn } from '@tanstack/solid-start'
import { fetchUsers } from '~/utils/users'
function UserList() {
const fetchUsersFn = useServerFn(fetchUsers)
const [users] = createResource(async () => {
return fetchUsersFn()
})
return (
<Suspense fallback={<div>Loading users...</div>}>
<For each={users()}>
{(user) => <div>{user.name}</div>}
</For>
</Suspense>
)
}
Data Loading
Deferred Loading
import { Suspense } from 'solid-js'
import { Await, createFileRoute } from '@tanstack/solid-router'
import { fetchPost, fetchComments } from '~/utils/posts'
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => ({
post: await fetchPost({ data: params.postId }),
// Deferred - loads after initial render
comments: fetchComments({ data: params.postId }),
}),
component: PostWithComments,
})
function PostWithComments() {
const data = Route.useLoaderData()
return (
<div>
<h1>{data().post.title}</h1>
<Suspense fallback={<div>Loading comments...</div>}>
<Await promise={data().comments}>
{(comments) => (
<For each={comments}>
{(comment) => <div>{comment.text}</div>}
</For>
)}
</Await>
</Suspense>
</div>
)
}
Progressive Enhancement
import { useServerFn } from '@tanstack/solid-start'
import { createSignal } from 'solid-js'
import { createUser } from '~/utils/users'
function SignupForm() {
const createUserFn = useServerFn(createUser)
const [pending, setPending] = createSignal(false)
const handleSubmit = async (e: SubmitEvent) => {
e.preventDefault()
setPending(true)
const formData = new FormData(e.currentTarget as HTMLFormElement)
try {
await createUserFn({
data: {
email: formData.get('email'),
password: formData.get('password'),
},
})
} finally {
setPending(false)
}
}
return (
<form onSubmit={handleSubmit}>
<input name="email" type="email" required />
<input name="password" type="password" required />
<button type="submit" disabled={pending()}>
{pending() ? 'Creating...' : 'Sign Up'}
</button>
</form>
)
}
Middleware
Authentication Middleware
import { createMiddleware } from '@tanstack/solid-start'
import { redirect } from '@tanstack/solid-router'
const authMiddleware = createMiddleware({ type: 'function' })
.server(async ({ next, context }) => {
const session = await getSession(context.request)
if (!session) {
throw redirect({ to: '/login' })
}
return next({ context: { user: session.user } })
})
const getProfile = createServerFn({ method: 'GET' })
.middleware([authMiddleware])
.handler(async ({ context }) => {
return { name: context.user.name }
})
Client-Only Code
Mark browser-only modules:
import '@tanstack/solid-start/client-only'
import { createSignal, onMount } from 'solid-js'
export function useLocalStorage(key: string) {
const [value, setValue] = createSignal(localStorage.getItem(key))
onMount(() => {
localStorage.setItem(key, value() ?? '')
})
return [value, setValue] as const
}
Server-Only Code
Mark server-only modules:
import '@tanstack/solid-start/server-only'
import { db } from '~/db'
export async function getUsers() {
return db.user.findMany()
}
Error Handling
import { createFileRoute, ErrorComponent } from '@tanstack/solid-router'
export const Route = createFileRoute('/')({
errorComponent: ({ error }) => (
<div>
<h1>Error</h1>
<pre>{error.message}</pre>
</div>
),
})
Streaming
Stream responses with RawStream:
import { createServerFn, RawStream } from '@tanstack/solid-start'
const streamData = createServerFn({ method: 'GET' }).handler(async () => {
return new RawStream(async (controller) => {
for (let i = 0; i < 10; i++) {
controller.send(`Chunk ${i}\n`)
await new Promise(r => setTimeout(r, 100))
}
controller.end()
})
})
Best Practices
Use Fine-Grained Reactivity
Leverage Solid’s fine-grained updates:
const [user, setUser] = createSignal({ name: 'John', age: 30 })
// ✅ Good - only updates name
setUser({ ...user(), name: 'Jane' })
// ✅ Even better - use stores for nested reactivity
const [user, setUser] = createStore({ name: 'John', age: 30 })
setUser('name', 'Jane')
Suspense for Loading States
Use Suspense boundaries:
<Suspense fallback={<Loading />}>
<AsyncComponent />
</Suspense>
Combine with Solid Query
Use Solid Query for advanced data fetching:
import { createQuery } from '@tanstack/solid-query'
import { useServerFn } from '@tanstack/solid-start'
function UserProfile() {
const fetchUserFn = useServerFn(fetchUser)
const query = createQuery(() => ({
queryKey: ['user', userId],
queryFn: async () => fetchUserFn({ data: userId }),
}))
return (
<Show when={query.data}>
{(user) => <div>{user().name}</div>}
</Show>
)
}
API Reference
Solid-Specific Exports
All exports from @tanstack/solid-start:
import {
createServerFn, // Create server functions
createMiddleware, // Create middleware
useServerFn, // Use server functions in components
RawStream, // Stream responses
// ... all other Start exports
} from '@tanstack/solid-start'
Differences from React
- Signals instead of State: Use
createSignal instead of useState
- No useEffect: Use
createEffect or onMount
- Control Flow: Use
<Show>, <For>, <Switch> instead of conditional rendering
- Fine-grained Reactivity: Updates are more granular and efficient