name: mfing-bible-of-tanstack description: The definitive TanStack guide covering Start, Router, and Query. Server functions with { data: params } pattern, route integration with loaderDeps, TanStack Query cache management with unified queryOptions, ServerOnly authentication, public API routes, webhooks. Use when creating routes, server functions, query hooks, mutations, implementing authentication, building webhooks, debugging data fetching, fixing loading spinners, or any TanStack Start/Router/Query work.
The MF'ing Bible of TanStack
The definitive guide to TanStack Start, Router, and Query patterns. This skill provides indexed access to comprehensive documentation without consuming context when not needed.
Version Reference
| Package | Version | Notes |
|---|---|---|
@tanstack/react-start |
1.145.x | fetch handler output (1.132+) |
@tanstack/react-router |
1.144.x | |
@tanstack/react-query |
5.80.x | |
@tanstack/router-plugin |
1.145.x | |
hono |
4.x | Recommended server runtime |
@hono/node-server |
1.x | Node.js adapter |
Quick Reference (Essential Patterns)
The Three Critical Patterns
- Server Function Parameters:
{ data: params }destructuring - Route Parameter Flow:
loaderDeps→loader→ server function - Unified QueryOptions: Single source of truth for cache keys
Server Function Template
import { createServerFn } from '@tanstack/react-start'
import { getServerAuth } from '@/lib/auth/serverAuth'
import { db, yourTable } from '@mysite/shared/db'
export const functionName = createServerFn({
method: 'GET', // or 'POST'
})
.inputValidator((params: YourParamsType) => params)
.handler(async ({ data: params }) => {
// 1. Auth context (if needed)
const authContext = await getServerAuth(['admin', 'manager'])
// 2. CRITICAL: Safe parameter handling
const safeParams = params || {}
// 3. Database operations
const results = await db.select().from(yourTable)
.where(/* conditions */)
.limit(safeParams.limit || 50)
.offset(safeParams.offset || 0)
return results
})
Route Template
import { createFileRoute } from '@tanstack/react-router'
import { serverFunction } from '@/lib/serverFunctions/yourFn'
export const Route = createFileRoute('/your/route/')(({
validateSearch: (search) => ({
param1: search?.param1 || undefined,
param2: search?.param2 || undefined,
}),
// loaderDeps: ONLY for search parameters (query strings)
loaderDeps: ({ search }) => ({
param1: search?.param1 || undefined,
param2: search?.param2 || undefined,
}),
loader: async ({ deps }) => {
const data = await serverFunction({ data: deps })
return { data }
},
component: YourComponent,
}))
Parameter Flow
URL: /route?search=test&status=active
↓
1. validateSearch → { search: 'test', status: 'active' }
↓
2. loaderDeps → { search: 'test', status: 'active' }
↓
3. loader → serverFunction({ data: deps })
↓
4. handler → ({ data: params }) where params = { search: 'test', status: 'active' }
Path Parameters vs Search Parameters
// PATH PARAMETERS: /users/$userId → automatically available
loader: async ({ params }) => {
// params.userId is automatic - NO loaderDeps needed
return getUserById({ data: { userId: params.userId } })
}
// SEARCH PARAMETERS: /users?search=test → requires loaderDeps
loaderDeps: ({ search }) => ({ search: search?.search }),
loader: async ({ deps }) => {
return searchUsers({ data: deps })
}
When to Consult Reference Files
| Task | Reference File |
|---|---|
| Creating server functions | references/server-functions.md |
| Server function composition/cross-domain | references/server-functions.md |
| Creating routes with loaderDeps | references/routes-and-loaders.md |
| Route file naming conventions | references/routes-and-loaders.md |
| Adding authentication/RBAC | references/authentication.md |
| ServerOnly pattern | references/authentication.md |
| Building webhooks/public API routes | references/api-routes-webhooks.md |
| Global middleware | references/api-routes-webhooks.md |
| TanStack Query integration | references/tanstack-query.md |
| Unified queryOptions pattern | references/tanstack-query.md |
| Cache invalidation/mutations | references/tanstack-query.md |
| Optimistic updates | references/tanstack-query.md |
| Fixing loading spinners | references/loading-states.md |
| SWR behavior configuration | references/loading-states.md |
| Debugging hydration issues | references/debugging.md |
| CJS/ESM problems | references/debugging.md |
| Testing server functions | references/debugging.md |
| Production deployment | references/production-deployment.md |
| Docker deployment | references/production-deployment.md |
| Static file serving | references/production-deployment.md |
| Hono server setup | references/production-deployment.md |
| Code review for mistakes | references/anti-patterns.md |
| Common errors to avoid | references/anti-patterns.md |
Critical Rules Summary
Server Functions
- Always use
{ data: params }destructuring in handler - Always handle undefined:
const safeParams = params || {} - Use shared database connection from
@/lib/db - Use
Fnsuffix for server function files:prospectsFn.ts - See
references/server-functions.mdfor composition patterns
Routes
loaderDepsis ONLY for search parameters (query strings)- Path parameters (
$prospectId) are automatically available inparams - Never use
loaderDepsfor path parameters - Use descriptive names not
index.tsx:prospects-pipeline.tsx - See
references/routes-and-loaders.mdfor complete patterns
Authentication
- Use
createServerOnlyFn- guarantees server-only execution - Never put auth logic directly in handlers
- Never use middleware pattern for auth (tree-shaking issues)
- See
references/authentication.mdfor ServerOnly pattern
TanStack Query
- Use singular cache keys matching DB names:
['prospect', filters] - Unified queryOptions shared between routes and hooks
- Never duplicate query definitions
- See
references/tanstack-query.mdfor cache management
Public API Routes
- Use
createFileRoutewithserver.handlersfor webhooks - Read raw body with
request.text()for signature verification - See
references/api-routes-webhooks.mdfor webhook patterns
File Structure Convention
apps/web/
├── src/
│ ├── lib/
│ │ ├── db.ts # Shared database connection
│ │ └── serverFunctions/
│ │ ├── prospectsFn.ts # Server functions with Fn suffix
│ │ ├── authFn.ts
│ │ └── campaignFn.ts
│ ├── routes/
│ │ └── admin/prospects/
│ │ ├── prospects-pipeline.tsx # Descriptive name (not index.tsx)
│ │ └── $prospectId/
│ │ └── prospect-details.tsx
│ └── hooks/
│ └── useProspects.ts
├── server.mjs # Hono production server wrapper
└── dist/ # Build output (after vite build)
├── server/server.js # TanStack fetch handler (NOT standalone!)
└── client/ # Static assets (JS, CSS, images)
Production Server (Quick Reference)
CRITICAL: TanStack Start outputs a fetch handler, NOT a runnable server. Wrap with Hono:
// server.mjs - Hono wrapper for TanStack Start
import { Hono } from 'hono'
import { serve } from '@hono/node-server'
import { serveStatic } from '@hono/node-server/serve-static'
import handler from './dist/server/server.js'
const app = new Hono()
// Static files FIRST (TanStack doesn't serve these!)
app.use('/assets/*', serveStatic({ root: './dist/client' }))
app.use('/images/*', serveStatic({ root: './dist/client' }))
// TanStack Start handles everything else
app.all('*', (c) => handler.fetch(c.req.raw))
serve({ fetch: app.fetch, port: 3001 })
See references/production-deployment.md for complete patterns.
Common Patterns
Cross-Domain Server Function Calls
// ✅ CORRECT: Use server functions for cross-domain queries
const contact = await getContactFullProfile({ data: { contactid: input.contactid } })
const product = await getProductDetails({ data: { productid: input.productid } })
// ❌ WRONG: Direct database queries across domains
const contact = await db.query.contact.findFirst(...) // Don't do this from invoiceFn
Same-Domain Direct Queries
// ✅ CORRECT: Direct queries within same domain
export const updateSubscription = createServerFn({ method: 'PUT' })
.handler(async ({ data: input }) => {
// Same-domain lookup - OK to use direct query
const existing = await db.query.subscription.findFirst({
where: eq(subscription.subscriptionid, input.subscriptionid)
})
// ...
})
Auth Patterns by Role
// No authentication required
const authContext = undefined // Skip getServerAuth
// Any authenticated user
const authContext = await getServerAuthAnyRole()
// Specific roles
const authContext = await getServerAuth(['admin'])
const authContext = await getServerAuth(['admin', 'manager'])
Reference Files
Detailed Documentation
references/server-functions.md- Server function patterns, composition, cross-domain queries, database connection, input validationreferences/routes-and-loaders.md- Route integration, loaderDeps, file naming, path vs search parametersreferences/authentication.md- ServerOnly auth, RBAC middleware, createServerOnlyFn, auth patterns by rolereferences/api-routes-webhooks.md- Public API routes, webhooks, raw body handling, global middlewarereferences/tanstack-query.md- QueryOptions, cache keys, mutations, optimistic updates, prefetchingreferences/loading-states.md- SWR behavior, pending components, spinner fixes, shouldReloadreferences/debugging.md- Hydration issues, CJS/ESM problems, testing setup, Vite configreferences/anti-patterns.md- Common mistakes, what NOT to do, parameter safety
Finding Specific Patterns
Use grep to search reference files:
grep -i "optimistic" references/tanstack-query.md
grep -i "webhook" references/api-routes-webhooks.md
grep -i "hydration" references/debugging.md
grep -i "loaderDeps" references/routes-and-loaders.md
Key Insight: Architecture
Our application is a modern full-stack React solution using:
- TanStack Start: Unified server/client routing with file-based routing
- TanStack Query: All client-side data fetching with SWR caching
- Server Functions: Type-safe RPC endpoints with middleware
This means:
- Single application (no separate backend)
- Automatic refetching on focus/reconnect
- Request deduplication
- SSR compatibility
- Type safety end-to-end
Never learn these patterns again. This is the way.