{"filename":"tanstack-start.mdc","content":"---\ndescription: \"TanStack Start full-stack React framework using server functions, API routes, SSR, streaming with defer(), and multi-platform deployment via Vinxi/Nitro\"\nglobs: [\"src/routes/**/*\", \"src/server/**/*\", \"app.config.ts\"]\nalwaysApply: false\n---\nYou are an expert in TanStack Start, TanStack Router, React, TypeScript, and full-stack type-safe web applications.\n\n## Core Principles\n- TanStack Start = TanStack Router + Vinxi (Vite + Nitro) for full-stack React\n- `createServerFn` is the primary way to run server-side logic with end-to-end type safety\n- All TanStack Router conventions apply — file-based routing, loaders, search params, etc.\n- Server functions replace REST endpoints for most use cases\n- Streaming + Suspense are first-class — use `defer()` for non-critical data\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: { plugins: [tsConfigPaths()] },\n  server: {\n    preset: 'node-server', // or: 'vercel', 'netlify', 'bun', 'cloudflare-pages'\n  },\n})\n```\n\n## Root Route HTML Shell\n```tsx\n// src/routes/__root.tsx\nexport const Route = createRootRoute({\n  component: () => (\n    <html lang=\"en\">\n      <head />\n      <body>\n        <Outlet />\n        <ScrollRestoration />\n        <Scripts />\n      </body>\n    </html>\n  ),\n})\n```\n\n## Server Functions\n```ts\n// src/server/functions/posts.ts\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 }) => db.post.create({ data }))\n```\n\n## Using Server Functions in Routes\n```tsx\nexport const Route = createFileRoute('/posts/$postId')({\n  loader: ({ params }) => getPost({ data: { id: params.postId } }),\n  component: PostDetail,\n})\n```\n\n## Mutations with Server Functions\n```tsx\nconst mutation = useMutation({\n  mutationFn: (input: { title: string; body: string }) => createPost({ data: input }),\n  onSuccess: () => queryClient.invalidateQueries({ queryKey: ['posts'] }),\n})\n```\n\n## API Routes (for webhooks / raw HTTP)\n```ts\n// src/routes/api/webhook.ts\nexport const Route = createAPIFileRoute('/api/webhook')({\n  POST: async ({ request }) => {\n    const body = await request.json()\n    return Response.json({ received: true })\n  },\n})\n```\n\n## Streaming with defer()\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\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}>{(c) => <CommentsList comments={c} />}</Await>\n      </Suspense>\n    </div>\n  )\n}\n```\n\n## Environment Variables\n- Access server-only vars via `process.env` inside server functions only\n- Use `import.meta.env.VITE_*` for client-exposed variables\n- Never access `process.env` in client components\n\n## Deployment Targets\nConfigure `server.preset` in `app.config.ts`:\n- `node-server` — default Node.js\n- `vercel` — Vercel serverless/edge\n- `netlify` — Netlify Functions\n- `bun` — Bun runtime\n- `cloudflare-pages` — Cloudflare Pages + Workers\n"}