Agent Skills: TanStack Start Execution Model

Use when deciding where TanStack Start code runs, fixing secret leaks, DB-in-loader bugs, or hydration mismatches.

UncategorizedID: fusengine/agents/start-execution-model

Install this agent skill to your local

pnpm dlx add-skill https://github.com/Fusengine/claude-plugins/tree/HEAD/plugins/tanstack-start-expert/skills/start-execution-model

Skill Files

Browse the full folder contents for start-execution-model.

Download Skill

Loading file tree…

plugins/tanstack-start-expert/skills/start-execution-model/SKILL.md

Skill Metadata

Name
start-execution-model
Description
Use when deciding where TanStack Start code runs, fixing secret leaks, DB-in-loader bugs, or hydration mismatches.
<objective> Covers TanStack Start's execution model — where code actually runs, described as the single most important concept in Start and the #1 source of AI-generated bugs. Targets @tanstack/react-start v1.168.58.

Every module is isomorphic by default: it runs in BOTH server and client bundles, and route loaders run on both server (SSR) and client (navigation) — so DB access, filesystem, and secrets must live inside createServerFn or createServerOnlyFn, never a bare loader or module scope. process.env must be read per request, not at module scope, because module-level reads can leak into the client bundle and are undefined on edge runtimes that inject env at request time. VITE_/PUBLIC_-prefixed vars are exposed to the client, so server secrets must never carry that prefix.

Documents every environment-boundary API at a glance — createServerFn, createServerOnlyFn, createClientOnlyFn, createIsomorphicFn, <ClientOnly>, useHydrated(), and the server-only/client-only import markers — with a decision framework for which to use and complete copy-paste boundary examples.

Do NOT use this skill for initial project setup (use start-core) or for file/directory SOLID organization (use solid-tanstack-start). </objective>

TanStack Start Execution Model

Understanding where code runs is the single most important concept in Start, and the #1 source of AI-generated bugs. Targets @tanstack/react-start v1.168.58.

CRITICAL — everything is isomorphic by default. Every module runs in BOTH the server and client bundles. Route loaders run on both — during SSR AND during client navigation. DB, filesystem, and secrets MUST live inside createServerFn (or createServerOnlyFn), never a bare loader or module scope.

CRITICAL — read process.env per request, not at module scope. Module-level reads (1) can be inlined into the client bundle (secret leak) and (2) are undefined on edge runtimes (Cloudflare Workers inject env at request time).

CRITICAL — VITE_/PUBLIC_ prefixed vars are exposed to the client. Server secrets must have NO public prefix.

Agent Workflow (MANDATORY)

Verify APIs against Context7 (/websites/tanstack_start_framework_react) + Exa before writing boundary code. After changes, run fuse-ai-pilot:sniper.


The Boundary APIs at a Glance

| API | Use case | Client behavior | Server behavior | |-----|----------|-----------------|-----------------| | createServerFn() | RPC, data, mutations | network request | direct execution | | createServerOnlyFn(fn) | server utility | throws | direct execution | | createClientOnlyFn(fn) | browser utility | direct execution | throws | | createIsomorphicFn() | per-env implementation | .client() impl | .server() impl | | <ClientOnly fallback> | browser-only UI | renders children | renders fallback | | useHydrated() | post-hydration logic | true after hydrate | false | | import '@tanstack/react-start/server-only' | whole file server-only | import denied | allowed | | import '@tanstack/react-start/client-only' | whole file client-only | allowed | import denied |

All boundary creators are imported from @tanstack/react-start; <ClientOnly> and useHydrated come from @tanstack/react-router.


The One Gotcha to Remember

// ❌ WRONG — loader is isomorphic; SECRET ships to the browser
export const Route = createFileRoute('/dashboard')({
  loader: async () => {
    const secret = process.env.API_SECRET // leaked
    return fetch(url, { headers: { Authorization: secret } })
  },
})

// ✅ CORRECT — server-only work behind createServerFn
const getData = createServerFn({ method: 'GET' }).handler(async () => {
  const secret = process.env.API_SECRET // stays on the server
  return fetch(url, { headers: { Authorization: secret } })
})
export const Route = createFileRoute('/dashboard')({ loader: () => getData() })

Reference Guide

| Topic | Reference | Load when | |-------|-----------|-----------| | Why loaders/modules are isomorphic; the mental model | references/isomorphic-by-default.md | deciding server vs client vs both | | Each boundary API + import markers + which to pick | references/environment-boundaries.md | choosing an API, marking a file | | VITE_/PUBLIC_ vs process.env, per-request reads, runtime vars | references/environment-variables.md | env var undefined / leaked | | Complete copy-paste examples of every boundary | references/templates/boundaries.md | writing boundary code |


Decision Framework

  • Server-only (createServerFn / createServerOnlyFn): secrets, DB, filesystem, external API keys.
  • Client-only (createClientOnlyFn / <ClientOnly>): DOM, localStorage, geolocation, analytics.
  • Isomorphic (default / createIsomorphicFn): formatting, business logic, shared utilities, loaders.

Forbidden

  • DB / secrets / filesystem in a bare loader (→ createServerFn).
  • process.env at module scope (leak + undefined on edge).
  • VITE_/PUBLIC_ prefix on a secret.
  • Non-deterministic render output (new Date() directly) → hydration mismatch.
  • Dynamic import() of a *.functions.ts server function (bundler issues).