PluginBench
Rule

tanstack start

via PatrickJS/awesome-cursorrules

Full-stack React framework with server functions, streaming, and end-to-end type safety via TanStack Router and Vinxi.

What is tanstack start?

TanStack Start is a file-based routing framework built on TanStack Router and Vinxi that enables server functions, API routes, streaming with defer(), and SSR. Use it when you need a full-stack React application with type-safe server-client communication and progressive rendering via Suspense.

  • Define server functions with createServerFn for type-safe server-side logic with Zod validation
  • Stream non-critical data using defer() and Suspense for progressive rendering
  • Create API routes with createAPIFileRoute for webhooks and third-party integrations
  • Access request context (cookies, headers) in server functions for authentication
  • Deploy to multiple targets (Node.js, Vercel, Netlify, Bun, Cloudflare) via app.config.ts preset
  • Integrate TanStack Query for loader-level data prefetching and cache management

Applies to

File patterns this rule matches.

**/*
Rule definition (reference)

Source of truth, from the repository.

You are an expert in TanStack Start, TanStack Router, React, TypeScript, Vinxi, and full-stack type-safe web applications.

TanStack Start Guidelines

What is TanStack Start

TanStack Start is a full-stack React framework built on top of TanStack Router and Vinxi (Vite + Nitro). It provides SSR, streaming, server functions, and API routes with end-to-end type safety.

Core Principles

  • TanStack Start is file-based routing via TanStack Router — all routing conventions apply
  • Server Functions (createServerFn) are the primary way to run server-side logic
  • Full-stack type safety: server function inputs/outputs are typed end-to-end
  • Streaming and Suspense are first-class — use them for progressive rendering
  • Start is NOT an API-first framework — server functions replace REST endpoints for most use cases

Project Structure

src/
  routes/
    __root.tsx          ← Root layout with HTML shell
    index.tsx           ← Home route
    posts/
      index.tsx
      $postId.tsx
  server/
    functions/          ← Server functions (recommended organization)
      posts.ts
      auth.ts
  lib/
    db.ts               ← Database client
    auth.ts             ← Auth utilities
app.config.ts           ← TanStack Start / Vinxi config

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 Setup

// src/routes/__root.tsx
import { createRootRoute, ScrollRestoration, Scripts, Outlet } from '@tanstack/react-router'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { TanStackRouterDevtools } from '@tanstack/router-devtools'

export const Route = createRootRoute({
  component: RootComponent,
})

function RootComponent() {
  return (
    <html lang="en">
      <head />
      <body>
        <Outlet />
        <ScrollRestoration />
        <Scripts />
        {process.env.NODE_ENV === 'development' && (
          <>
            <TanStackRouterDevtools />
            <ReactQueryDevtools />
          </>
        )}
      </body>
    </html>
  )
}

Server Functions

  • Use createServerFn to define functions that always run on the server
  • Validate inputs with Zod using .validator()
  • Use .handler() for the implementation
  • Server functions are called like regular async functions from components or loaders
// src/server/functions/posts.ts
import { createServerFn } from '@tanstack/start'
import { z } from 'zod'

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, context }) => {
    // context has access to request headers, cookies, etc.
    return db.post.create({ data })
  })

Using Server Functions in Routes

// src/routes/posts/$postId.tsx
import { createFileRoute } from '@tanstack/react-router'
import { getPost } from '../../server/functions/posts'

export const Route = createFileRoute('/posts/$postId')({
  loader: ({ params }) => getPost({ data: { id: params.postId } }),
  component: PostDetail,
})

function PostDetail() {
  const post = Route.useLoaderData()
  return <article><h1>{post.title}</h1></article>
}

Mutations with Server Functions

  • Call server functions directly in event handlers or via TanStack Query mutations
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { createPost } from '../../server/functions/posts'

function CreatePostForm() {
  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 fd = new FormData(e.currentTarget)
      mutation.mutate({ title: fd.get('title') as string, body: fd.get('body') as string })
    }}>
      <input name="title" />
      <textarea name="body" />
      <button type="submit" disabled={mutation.isPending}>
        {mutation.isPending ? 'Creating...' : 'Create'}
      </button>
    </form>
  )
}

API Routes

  • Use createAPIFileRoute for raw HTTP endpoints (webhooks, third-party integrations)
  • Place in src/routes/api/ directory
// 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()
    // handle webhook
    return Response.json({ received: true })
  },
})

Streaming & Suspense

  • Use defer() to stream non-critical data after the initial render
  • Wrap deferred data consumers in <Suspense>
export const Route = createFileRoute('/posts/$postId')({
  loader: async ({ params }) => {
    const post = await getPost({ data: { id: params.postId } }) // awaited (critical)
    const comments = getComments({ data: { postId: params.postId } }) // not awaited (deferred)
    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}>
          {(resolved) => <CommentsList comments={resolved} />}
        </Await>
      </Suspense>
    </div>
  )
}

Authentication

  • Read cookies/headers in server functions using TanStack Start's server context
  • Use beforeLoad in routes for auth guards
import { getWebRequest } from '@tanstack/start/server'

export const getSession = createServerFn().handler(async () => {
  const request = getWebRequest()
  const sessionToken = getCookie(request, 'session')
  return validateSession(sessionToken)
})

Deployment Targets

  • node-server — default Node.js server
  • vercel — Vercel serverless/edge
  • netlify — Netlify Functions
  • bun — Bun runtime
  • cloudflare-pages — Cloudflare Pages + Workers
  • Configure in app.config.ts under server.preset

Environment Variables

  • Access server-only vars directly from process.env inside server functions
  • Use Vite's import.meta.env for client-exposed variables (prefix with VITE_)
  • Never access process.env in client components

TanStack Query Integration

  • Provide QueryClient via router context for loader-level prefetching
  • Use ensureQueryData in loaders to populate cache before render
  • This eliminates loading states for route-level data fetching

Related rules

Senior full-stack TypeScript, React, Node.js guidance with clean architecture, testing, and WHY-oriented reasoning.

**/*
41k
via PatrickJS/awesome-cursorrules

Quantitative factor research skills for designing, evaluating, and mining alpha factors in equities markets.

**/*
41k
via PatrickJS/awesome-cursorrules

Android development with Jetpack Compose, clean architecture, and Material Design 3.

**/*
41k
via PatrickJS/awesome-cursorrules

Angular development with Novo Elements UI library using standalone components.

**/*
41k
via PatrickJS/awesome-cursorrules

Expert Angular 18 + TypeScript development with Jest, emphasizing clean code and performance.

**/*
41k
via PatrickJS/awesome-cursorrules

Manage Kubernetes clusters, add-ons, stacks, and credentials via the Ankra CLI platform.

**/*.sh +5
41k
via PatrickJS/awesome-cursorrules