agentleFS
FirzusFirzus/agent-skills/skills/web/tanstack-start/SKILL.md

tanstack-start

Skill4 starsChanged 20 days agoMITView on GitHub

TanStack Start, the full-stack React framework built on TanStack Router with Vite or Rsbuild. Use when setting up or reviewing a `@tanstack/react-start` project — the build-tool plugin, SSR and selective SSR, server functions (`createServerFn`), server routes, middleware, prerendering, SPA mode, environment variables, or deployment targets.

  • Reads credentials

8 other projects use a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

10 rules. 0 are common, 2 are distinctive, and 8 are very specific to this project.

  • 0

    Common

    Most of the 5 edited versions we read keep these.

  • 2

    Distinctive

    This author's own choices. Worth a look.

  • 8

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Start is at Release Candidate stage: the API is considered stable and feature-complete, but not bug-free.

    line 24
  2. Very specific:

    Read the build config (vite.config.ts / rsbuild.config.ts) before changing anything: prerender, spa, pages, serverFns, and server.build all …

    line 47
  3. Very specific:

    Look for src/start.ts. If it exists, Start does not auto-install the CSRF middleware — that file owns global request and function …

    line 50
  4. Distinctive:

    Check whether a host adapter plugin is already wired in (Cloudflare, Netlify, Nitro) — plugin order in the array matters.

    line 54
  5. Very specific:

    Every route matching the initial request is server-rendered by default.

    line 190
  6. Very specific:

    Always call next(); the chain stops otherwise.

    line 225
  7. Very specific:

    Start loads .env, .env.development, .env.production, and .env.local automatically.

    line 233
  8. Very specific:

    Read process.env inside handlers and middleware, not at module scope.

    line 239
  9. Very specific:

    Every server function has a .validator() when it takes input, and the validated value is checked for authorization, not just shape — a …

    line 253
  10. Very specific:

    No secret is read at module scope, and no secret sits behind a VITE_ or PUBLIC_ prefix.

    line 259

“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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

TanStack Start

Reference for TanStack Start, a full-stack React framework powered by TanStack Router. Start adds full-document SSR, streaming, server functions, server routes, middleware, and full-stack builds on top of Router; Router itself still owns routing, loaders, search params, and navigation.

Routing belongs to the sibling skill — see tanstack-router for file-based routes, loaders, search-param validation, and navigation. This skill covers only what Start adds on top. For server-state caching inside components, see tanstack-query.

Start is at Release Candidate stage: the API is considered stable and feature-complete, but not bug-free.

Branch-specific references, loaded on demand:

  • server-functions.md — createServerFn, validators, serialization, errors, redirects, streaming, static server functions.
  • middleware.md — request vs function middleware, context passing, global middleware in src/start.ts, CSRF.
  • server-routes.md — HTTP endpoints defined with the server property on file routes.
  • rendering.md — selective SSR, SPA mode, static prerendering, and ISR-style cache headers.
  • deployment.md — hosting targets, environment variables, and build outputs.

First Checks

  1. Confirm @tanstack/react-start and @tanstack/react-router are installed, and which build tool the project uses — Vite (@tanstack/react-start/plugin/vite) or Rsbuild (@tanstack/react-start/plugin/rsbuild). The plugin import path and several config shapes differ between them.
  2. Read the build config (vite.config.ts / rsbuild.config.ts) before changing anything: prerender, spa, pages, serverFns, and server.build all live in the tanstackStart() options.
  3. Look for src/start.ts. If it exists, Start does not auto-install the CSRF middleware — that file owns global request and function middleware.
  4. Find src/router.tsx (router creation) and src/routes/__root.tsx (the document shell with HeadContent and Scripts); both are required.
  5. Check whether a host adapter plugin is already wired in (Cloudflare, Netlify, Nitro) — plugin order in the array matters.

Project Setup

Scaffold with npx @tanstack/cli@latest create, or clone an official example with npx gitpick TanStack/router/tree/main/examples/react/start-basic.

Minimal dependency set: @tanstack/react-start, @tanstack/react-router, react, react-dom, plus the build tool (vite + @vitejs/plugin-react, or @rsbuild/core + @rsbuild/plugin-react).

// vite.config.ts
import { defineConfig } from 'vite'
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
import viteReact from '@vitejs/plugin-react'

export default defineConfig({
  server: { port: 3000 },
  plugins: [
    tanstackStart(),
    // react's vite plugin must come after start's vite plugin
    viteReact(),
  ],
})

Rules that bite:

  • The React plugin must come after tanstackStart() in the Vite plugin array.
  • package.json needs "type": "module".
  • In tsconfig.json, keep verbatimModuleSyntax disabled — enabling it can leak server bundles into client bundles. Use "jsx": "react-jsx", "moduleResolution": "Bundler", "module": "ESNext", strictNullChecks.
  • src/routeTree.gen.ts is generated on first run; do not hand-edit it.

Two files are required beyond the config:

// src/router.tsx
import { createRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'

export function getRouter() {
  return createRouter({ routeTree, scrollRestoration: true })
}
// src/routes/__root.tsx
import {
  createRootRoute,
  HeadContent,
  Outlet,
  Scripts,
} from '@tanstack/react-router'

export const Route = createRootRoute({
  head: () => ({
    meta: [{ charSet: 'utf-8' }, { title: 'App' }],
  }),
  component: () => (
    <html>
      <head>
        <HeadContent />
      </head>
      <body>
        <Outlet />
        <Scripts />
      </body>
    </html>
  ),
})

HeadContent and Scripts are not optional: without them, head tags and the client bundle never reach the document.

Server Functions vs Server Routes

Both run on the server; they answer different questions.

NeedUseWhy
Call server logic from your own app (loaders, components, handlers)createServerFnStart handles serialization and typing across the boundary.
An HTTP endpoint called from outside the app (webhooks, third parties, OAuth callbacks)Server route (server.handlers on a file route)Raw Request/Response, no Start client required.

Server functions are same-origin RPC endpoints protected by CSRF checks; they are not a public API surface. Server routes are.

import { createServerFn } from '@tanstack/react-start'
import { z } from 'zod'

const CreateUser = z.object({ name: z.string().min(1) })

export const createUser = createServerFn({ method: 'POST' })
  .validator(CreateUser)
  .handler(async ({ data }) => {
    return db.users.create(data)
  })

Details in server-functions.md and server-routes.md.

Where Code Runs

Route loader and beforeLoad are isomorphic: they run on the server for the initial request and on the client for subsequent navigations. Reading process.env.SECRET in a loader exposes it to the client bundle. Put server-only work behind a server function.

import {
  createServerOnlyFn,
  createClientOnlyFn,
  createIsomorphicFn,
} from '@tanstack/react-start'

const getSecret = createServerOnlyFn(() => process.env.API_SECRET)
const saveLocal = createClientOnlyFn((v: string) => localStorage.setItem('k', v))
const log = createIsomorphicFn()
  .server((m: string) => console.log(`[SERVER] ${m}`))
  .client((m: string) => console.log(`[CLIENT] ${m}`))

Server functions can be statically imported anywhere, including client components — the build replaces the implementation with an RPC stub. Avoid await import() of server-function modules; dynamic imports break that transformation.

Rendering Strategy

Every route matching the initial request is server-rendered by default. Narrow it per route with ssr:

ValuebeforeLoad / loader on serverComponent SSR
true (default)yesyes
'data-only'yesno
falsenono

Change the app-wide default with defaultSsr in createStart. Inheritance is one-way: a child can only make the inherited value more restrictive, so ssr: true under a parent with ssr: false has no effect.

For a fully client-rendered app, use SPA mode (spa: { enabled: true }), which prerenders a shell to /_shell.html. See rendering.md.

Middleware

createMiddleware() produces request middleware, which runs for every server request including SSR and server functions. createMiddleware({ type: 'function' }) produces server-function middleware, which additionally supports .client() and .validator().

import { createMiddleware } from '@tanstack/react-start'

export const authMiddleware = createMiddleware().server(
  async ({ next, request }) => {
    const session = await auth.getSession({ headers: request.headers })
    if (!session) throw new Error('Unauthorized')
    return next({ context: { session } })
  },
)

Always call next(); the chain stops otherwise. Attach auth middleware to every server function that reads or writes private data — a route beforeLoad guard is UX, not the data boundary, because server functions are reachable independently of the UI that calls them. Full model in middleware.md.

Environment Variables

Start loads .env, .env.development, .env.production, and .env.local automatically.

  • Server code reads any variable from process.env.
  • Client code only sees the build tool's public prefix: VITE_ for Vite, PUBLIC_ for Rsbuild, via import.meta.env.
  • Read process.env inside handlers and middleware, not at module scope. On Cloudflare Workers and similar edge runtimes, env is injected per request, so module-scope reads evaluate to undefined.
  • To get a runtime (not build-time) value to the client, return it from a server function and pass it through a loader.

More, including typing and staticNodeEnv, in deployment.md.

Review Checklist

  • Build config is coherent: tanstackStart() before the React plugin (Vite), "type": "module", verbatimModuleSyntax disabled.
  • __root.tsx renders HeadContent and Scripts; routeTree.gen.ts is generated, not hand-edited.
  • Every server function has a .validator() when it takes input, and the validated value is checked for authorization, not just shape — a well-formed id is not an authorized id.
  • Auth middleware is attached to the server functions and server routes that touch private data, not only to route beforeLoad.
  • src/start.ts, when present, installs createCsrfMiddleware() explicitly.
  • No secret is read at module scope, and no secret sits behind a VITE_ or PUBLIC_ prefix.
  • Loaders contain no server-only assumptions; server work goes through createServerFn or createServerOnlyFn.
  • Cache-Control on anything identity-dependent is private or no-store, never public (rendering.md).
  • Routes needing browser APIs set ssr: false or 'data-only' and provide a pendingComponent fallback.
  • SPA-mode and static-host redirects allow /_serverFn/* and server-route paths through to the server (rendering.md).
  • Deployment target matches the build output and start script (deployment.md).

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.