{"filename":"tanstack-start-cursorrules-prompt-file.mdc","content":"---\ndescription: \"Cursor rules for TanStack Start full-stack React framework including server functions, API routes, streaming with defer(), SSR, and multi-platform deployment.\"\nglobs: **/*\nalwaysApply: false\n---\nYou are an expert in TanStack Start, TanStack Router, React, TypeScript, Vinxi, and full-stack type-safe web applications.\n\n# TanStack Start Guidelines\n\n## What is TanStack Start\nTanStack 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.\n\n## Core Principles\n- TanStack Start is file-based routing via TanStack Router — all routing conventions apply\n- Server Functions (`createServerFn`) are the primary way to run server-side logic\n- Full-stack type safety: server function inputs/outputs are typed end-to-end\n- Streaming and Suspense are first-class — use them for progressive rendering\n- Start is NOT an API-first framework — server functions replace REST endpoints for most use cases\n\n## Project Structure\n```\nsrc/\n  routes/\n    __root.tsx          ← Root layout with HTML shell\n    index.tsx           ← Home route\n    posts/\n      index.tsx\n      $postId.tsx\n  server/\n    functions/          ← Server functions (recommended organization)\n      posts.ts\n      auth.ts\n  lib/\n    db.ts               ← Database client\n    auth.ts             ← Auth utilities\napp.config.ts           ← TanStack Start / Vinxi config\n```\n\n## app.config.ts\n```ts\nimport { defineConfig } from '@tanstack/start/config'\nimport tsConfigPaths from 'vite-tsconfig-paths'\n\nexport default defineConfig({\n  vite: {\n    plugins: [tsConfigPaths()],\n  },\n  server: {\n    preset: 'node-server', // or 'vercel', 'netlify', 'bun', 'cloudflare-pages'\n  },\n})\n```\n\n## Root Route Setup\n```tsx\n// src/routes/__root.tsx\nimport { createRootRoute, ScrollRestoration, Scripts, Outlet } from '@tanstack/react-router'\nimport { ReactQueryDevtools } from '@tanstack/react-query-devtools'\nimport { TanStackRouterDevtools } from '@tanstack/router-devtools'\n\nexport const Route = createRootRoute({\n  component: RootComponent,\n})\n\nfunction RootComponent() {\n  return (\n    <html lang=\"en\">\n      <head />\n      <body>\n        <Outlet />\n        <ScrollRestoration />\n        <Scripts />\n        {process.env.NODE_ENV === 'development' && (\n          <>\n            <TanStackRouterDevtools />\n            <ReactQueryDevtools />\n          </>\n        )}\n      </body>\n    </html>\n  )\n}\n```\n\n## Server Functions\n- Use `createServerFn` to define functions that always run on the server\n- Validate inputs with Zod using `.validator()`\n- Use `.handler()` for the implementation\n- Server functions are called like regular async functions from components or loaders\n```ts\n// src/server/functions/posts.ts\nimport { createServerFn } from '@tanstack/start'\nimport { z } from 'zod'\n\nexport const getPost = createServerFn()\n  .validator(z.object({ id: z.string() }))\n  .handler(async ({ data }) => {\n    const post = await db.post.findUnique({ where: { id: data.id } })\n    if (!post) throw new Error('Post not found')\n    return post\n  })\n\nexport const createPost = createServerFn()\n  .validator(z.object({ title: z.string().min(1), body: z.string() }))\n  .handler(async ({ data, context }) => {\n    // context has access to request headers, cookies, etc.\n    return db.post.create({ data })\n  })\n```\n\n## Using Server Functions in Routes\n```tsx\n// src/routes/posts/$postId.tsx\nimport { createFileRoute } from '@tanstack/react-router'\nimport { getPost } from '../../server/functions/posts'\n\nexport const Route = createFileRoute('/posts/$postId')({\n  loader: ({ params }) => getPost({ data: { id: params.postId } }),\n  component: PostDetail,\n})\n\nfunction PostDetail() {\n  const post = Route.useLoaderData()\n  return <article><h1>{post.title}</h1></article>\n}\n```\n\n## Mutations with Server Functions\n- Call server functions directly in event handlers or via TanStack Query mutations\n```tsx\nimport { useMutation, useQueryClient } from '@tanstack/react-query'\nimport { createPost } from '../../server/functions/posts'\n\nfunction CreatePostForm() {\n  const queryClient = useQueryClient()\n  const mutation = useMutation({\n    mutationFn: (input: { title: string; body: string }) =>\n      createPost({ data: input }),\n    onSuccess: () => {\n      queryClient.invalidateQueries({ queryKey: ['posts'] })\n    },\n  })\n\n  return (\n    <form onSubmit={(e) => {\n      e.preventDefault()\n      const fd = new FormData(e.currentTarget)\n      mutation.mutate({ title: fd.get('title') as string, body: fd.get('body') as string })\n    }}>\n      <input name=\"title\" />\n      <textarea name=\"body\" />\n      <button type=\"submit\" disabled={mutation.isPending}>\n        {mutation.isPending ? 'Creating...' : 'Create'}\n      </button>\n    </form>\n  )\n}\n```\n\n## API Routes\n- Use `createAPIFileRoute` for raw HTTP endpoints (webhooks, third-party integrations)\n- Place in `src/routes/api/` directory\n```ts\n// src/routes/api/webhook.ts\nimport { createAPIFileRoute } from '@tanstack/start/api'\n\nexport const Route = createAPIFileRoute('/api/webhook')({\n  POST: async ({ request }) => {\n    const body = await request.json()\n    // handle webhook\n    return Response.json({ received: true })\n  },\n})\n```\n\n## Streaming & Suspense\n- Use `defer()` to stream non-critical data after the initial render\n- Wrap deferred data consumers in `<Suspense>`\n```tsx\nexport const Route = createFileRoute('/posts/$postId')({\n  loader: async ({ params }) => {\n    const post = await getPost({ data: { id: params.postId } }) // awaited (critical)\n    const comments = getComments({ data: { postId: params.postId } }) // not awaited (deferred)\n    return { post, comments: defer(comments) }\n  },\n  component: PostDetail,\n})\n\nfunction PostDetail() {\n  const { post, comments } = Route.useLoaderData()\n  return (\n    <div>\n      <h1>{post.title}</h1>\n      <Suspense fallback={<CommentsSkeleton />}>\n        <Await promise={comments}>\n          {(resolved) => <CommentsList comments={resolved} />}\n        </Await>\n      </Suspense>\n    </div>\n  )\n}\n```\n\n## Authentication\n- Read cookies/headers in server functions using TanStack Start's server context\n- Use `beforeLoad` in routes for auth guards\n```ts\nimport { getWebRequest } from '@tanstack/start/server'\n\nexport const getSession = createServerFn().handler(async () => {\n  const request = getWebRequest()\n  const sessionToken = getCookie(request, 'session')\n  return validateSession(sessionToken)\n})\n```\n\n## Deployment Targets\n- `node-server` — default Node.js server\n- `vercel` — Vercel serverless/edge\n- `netlify` — Netlify Functions\n- `bun` — Bun runtime\n- `cloudflare-pages` — Cloudflare Pages + Workers\n- Configure in `app.config.ts` under `server.preset`\n\n## Environment Variables\n- Access server-only vars directly from `process.env` inside server functions\n- Use Vite's `import.meta.env` for client-exposed variables (prefix with `VITE_`)\n- Never access `process.env` in client components\n\n## TanStack Query Integration\n- Provide `QueryClient` via router context for loader-level prefetching\n- Use `ensureQueryData` in loaders to populate cache before render\n- This eliminates loading states for route-level data fetching\n"}