tanstack-start
Skill275 starsChanged 34 days agoApache-2.0View on GitHub
Best practices for TanStack Start, the full-stack React framework built on TanStack Router and Vite/Nitro. Use when building server functions with createServerFn, setting up SSR and streaming with defer()/Await, writing API routes, configuring app.config.ts, or deploying a TanStack Start app to Node, Vercel, Netlify, Bun, or Cloudflare.
- Reads credentials
8 other projects use a version of this.
Adopted by
Most recently taken up by
- vercel-labs/json-rendertheir own version, first seen Oct 29, 2026
- Firzus/agent-skillstheir own version, first seen Oct 23, 2026
- itsLeonB/agent-config-managertheir own version, first seen Oct 20, 2026
- rockcookies/skillstheir own version, first seen Oct 13, 2026
- joelhooks/joelclawtheir own version, first seen Oct 8, 2026
- authdog/agent-skillstheir own version, first seen Sep 18, 2026
Other versions8 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 8
- vercel-labs/json-render★ 19ksame name
- dlupiak/claude-session-dashboard★ 66same name
- joelhooks/joelclaw★ 65same name
- Firzus/agent-skills★ 4same name
- Aver005/agent-skills★ 2same name
- rockcookies/skills★ 1same name
- authdog/agent-skills★ 0same name
- itsLeonB/agent-config-manager★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository264 other files it gives its agents.
- accessibility-a11ySkill
- alpine-jsSkill
- analytics-data-analysisSkill
- android-developmentSkill
- angular-developmentSkill
- angularSkill
- anime-jsSkill
- anthropic-claude-developmentSkill
- api-developmentSkill
- apollo-graphqlSkill
- aspnet-coreSkill
- astroSkill
- auth0-authenticationSkill
- autogen-developmentSkill
- automl-hyperparameter-optimizationSkill
- aws-developmentSkill
- azureSkill
- backend-developmentSkill
- bash-scriptingSkill
- beautifulsoup-parsingSkill
- bitbucket-workflowSkill
- blazorSkill
- blender-python-addonSkill
- blockchainSkill
- bootstrapSkill
- business-central-developmentSkill
- cheerio-parsingSkill
- chrome-extension-developmentSkill
- ci-cd-best-practicesSkill
- clean-architectureSkill
- clean-codeSkill
- clerk-authenticationSkill
- cloudflare-developmentSkill
- computer-vision-opencvSkill
- convexSkill
- cppSkill
- c-sharpSkill
- cssSkill
- cypressSkill
- data-analysis-jupyterSkill
- data-analystSkill
- data-jupyter-pythonSkill
- deep-learning-pythonSkill
- deep-learning-pytorchSkill
- deep-learningSkill
- deno-typescriptSkill
- design-systemsSkill
- devopsSkill
- django-pythonSkill
- django-rest-api-developmentSkill
- dockerSkill
- dotnetSkill
- drizzle-ormSkill
- drupal-developmentSkill
- elasticsearch-best-practicesSkill
- electron-developmentSkill
- elixirSkill
- embedded-stm32Skill
- esbuild-bundlerSkill
- ethereumSkill
- The first 60 shown.
The file
We split this skill into the rules it gives an agent
6 rules. 0 are common, 3 are distinctive, and 3 are very specific to this project.
0
Common
Most of the 5 edited versions we read keep these.
3
Distinctive
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 27
All TanStack Router conventions apply on top of Start: file-based routing, loaders, search param validation, and router context.
- Distinctive: line 29
Keep server-only code (secrets, DB clients, filesystem access) strictly inside server functions or API routes — never import it into client …
- Very specific: line 71
Always validate input with .validator() (Zod is the common choice) before the handler runs — never trust client-supplied data.
- Distinctive: line 143
Use API routes for endpoints that must speak raw HTTP (webhooks, OAuth callbacks, health checks) instead of server functions, which are …
- Very specific: line 189
Use import.meta.env.VITE_* for any variable that must be exposed to the client bundle.
- Very specific: line 190
Never reference process.env directly in a client component — Vite will not inline server secrets, and doing so is a common source of leaked …
“Common” is measured: kept by at least 6 in 10 of the 5 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
TanStack Start
TanStack Start is a full-stack React framework that layers server functions, SSR, streaming, and API routes on top of TanStack Router and a Vite/Nitro build pipeline, giving end-to-end type safety from server to client.
Workflow for Building a TanStack Start Feature
- Configure the app — Set up
app.config.tswith the Vite plugins and aserver.presetmatching the deployment target. - Define the root route — Create
src/routes/__root.tsxwith the HTML document shell (<html>,<head>,<body>,<Outlet>,<ScrollRestoration>,<Scripts>). - Write server functions — Use
createServerFn()with a.validator()and.handler()for any logic that must run on the server (database access, secrets, file I/O). - Wire server functions into routes — Call server functions from route
loaders for reads and from mutation hooks (useMutation) for writes. - Add API routes for raw HTTP — Use
createAPIFileRoutefor webhooks, OAuth callbacks, or endpoints that external services call directly. - Stream non-critical data — Wrap slow, non-blocking loader data in
defer()and render it with<Suspense>+<Await>. - Deploy — Pick the
server.presetfor the target platform and verify environment variable access rules before shipping.
Core Principles
- TanStack Start = TanStack Router + Vite + Nitro, giving full-stack React with a single type-safe codebase.
createServerFnis the primary mechanism for server-side logic — prefer it over hand-rolled REST endpoints for internal data access.- All TanStack Router conventions apply on top of Start: file-based routing, loaders, search param validation, and router context.
- Streaming and Suspense are first-class citizens; use
defer()for anything that shouldn't block the initial response. - Keep server-only code (secrets, DB clients, filesystem access) strictly inside server functions or API routes — never import it into client components.
Configuration
app.config.ts
import { defineConfig } from '@tanstack/start/config'
import tsConfigPaths from 'vite-tsconfig-paths'
export default defineConfig({
vite: { plugins: [tsConfigPaths()] },
server: {
preset: 'node-server', // or: 'vercel', 'netlify', 'bun', 'cloudflare-pages'
},
})Root Route HTML Shell
// src/routes/__root.tsx
import { createRootRoute, Outlet, ScrollRestoration } from '@tanstack/react-router'
import { Scripts } from '@tanstack/start'
export const Route = createRootRoute({
component: () => (
<html lang="en">
<head />
<body>
<Outlet />
<ScrollRestoration />
<Scripts />
</body>
</html>
),
})Server Functions
- Define server functions in a dedicated
src/server/functions/(or colocated*.server.ts) module for discoverability. - Always validate input with
.validator()(Zod is the common choice) before the handler runs — never trust client-supplieddata. - Throw descriptive
Errors (or a typed error) from handlers; TanStack Start serializes thrown errors back to the client. - Server functions can be called directly from loaders, actions, event handlers, or
useMutation— they behave like normal async functions on the client but execute on the server.
// src/server/functions/posts.ts
import { createServerFn } from '@tanstack/start'
import { z } from 'zod'
import { db } from '~/server/db'
export const getPost = createServerFn()
.validator(z.object({ id: z.string() }))
.handler(async ({ data }) => {
const post = await db.post.findUnique({ where: { id: data.id } })
if (!post) throw new Error('Post not found')
return post
})
export const createPost = createServerFn()
.validator(z.object({ title: z.string().min(1), body: z.string() }))
.handler(async ({ data }) => db.post.create({ data }))Using Server Functions in Routes
export const Route = createFileRoute('/posts/$postId')({
loader: ({ params }) => getPost({ data: { id: params.postId } }),
component: PostDetail,
})
function PostDetail() {
const post = Route.useLoaderData()
return <h1>{post.title}</h1>
}Mutations with Server Functions
import { useMutation } from '@tanstack/react-query'
import { useQueryClient } from '@tanstack/react-query'
import { createPost } from '~/server/functions/posts'
function NewPostForm() {
const queryClient = useQueryClient()
const mutation = useMutation({
mutationFn: (input: { title: string; body: string }) => createPost({ data: input }),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['posts'] }),
})
return (
<form
onSubmit={(e) => {
e.preventDefault()
const form = new FormData(e.currentTarget)
mutation.mutate({
title: String(form.get('title')),
body: String(form.get('body')),
})
}}
>
<input name="title" placeholder="Title" required />
<textarea name="body" placeholder="Body" required />
<button type="submit" disabled={mutation.isPending}>Publish</button>
</form>
)
}API Routes
Use API routes for endpoints that must speak raw HTTP (webhooks, OAuth callbacks, health checks) instead of server functions, which are meant to be called from your own client code.
// src/routes/api/webhook.ts
import { createAPIFileRoute } from '@tanstack/start/api'
export const Route = createAPIFileRoute('/api/webhook')({
POST: async ({ request }) => {
const body = await request.json()
// verify signature, enqueue processing, etc.
return Response.json({ received: true })
},
})Streaming with defer()
- Await only the data required for the initial paint; wrap everything else in
defer()so the shell renders immediately. - Pair deferred values with
<Suspense>and<Await>so the UI shows a fallback until the promise resolves.
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => {
const post = await getPost({ data: { id: params.postId } }) // critical, awaited
const comments = getComments({ data: { postId: params.postId } }) // not awaited
return { post, comments: defer(comments) }
},
component: PostDetail,
})
function PostDetail() {
const { post, comments } = Route.useLoaderData()
return (
<div>
<h1>{post.title}</h1>
<Suspense fallback={<CommentsSkeleton />}>
<Await promise={comments}>{(c) => <CommentsList comments={c} />}</Await>
</Suspense>
</div>
)
}Environment Variables
- Access server-only secrets via
process.envonly inside server functions or API routes. - Use
import.meta.env.VITE_*for any variable that must be exposed to the client bundle. - Never reference
process.envdirectly in a client component — Vite will not inline server secrets, and doing so is a common source of leaked configuration.
Deployment Targets
Configure server.preset in app.config.ts to match the hosting platform:
node-server— default, runs anywhere Node.js runs.vercel— Vercel serverless/edge functions.netlify— Netlify Functions.bun— Bun runtime.cloudflare-pages— Cloudflare Pages + Workers (watch for Node API compatibility gaps in server functions).
Common Mistakes
- Calling a server function's handler logic directly from a client component instead of going through
createServerFn— this breaks the client/server boundary and type inference. - Awaiting everything in a loader, which defeats the purpose of streaming and slows the initial response.
- Forgetting
.validator(), leaving handlers to trust unvalidated client input. - Mixing
process.envreads into shared utility modules that are imported by both client and server code.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

