plugins/tanstack-start-expert/skills/start-execution-model/SKILL.md
TanStack Start execution model — isomorphic-by-default, environment boundary functions (createServerFn, createServerOnlyFn, createClientOnlyFn, createIsomorphicFn), <ClientOnly>, useHydrated(), server-only/client-only import markers, and env-var safety (VITE_/PUBLIC_ prefix vs process.env). Use when: deciding where code runs, fixing secret leaks, DB/filesystem in a loader, hydration mismatches, "process.env is undefined on the server". Do NOT use for: initial setup (use start-core), file/directory SOLID organization (use solid-tanstack-start).
npx skillsauth add fusengine/agents start-execution-modelInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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.166.2.
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(orcreateServerOnlyFn), never a bare loader or module scope.CRITICAL — read
process.envper request, not at module scope. Module-level reads (1) can be inlined into the client bundle (secret leak) and (2) areundefinedon 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.
Verify APIs against Context7 (/websites/tanstack_start_framework_react) + Exa
before writing boundary code. After changes, run fuse-ai-pilot:sniper.
| 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.
// ❌ 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() })
| 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 |
createServerFn / createServerOnlyFn): secrets, DB,
filesystem, external API keys.createClientOnlyFn / <ClientOnly>): DOM, localStorage,
geolocation, analytics.createIsomorphicFn): formatting, business logic,
shared utilities, loaders.createServerFn).process.env at module scope (leak + undefined on edge).VITE_/PUBLIC_ prefix on a secret.new Date() directly) → hydration mismatch.import() of a *.functions.ts server function (bundler issues).testing
Copy self-audit and ban-lists — filler verbs/hype adjectives, slop placeholder names, fake-precise numbers, Title Case headlines, humor in error copy ('Oops!'), em-dash crutch, one copy register per page.
development
Logged-in web apps — dashboards, auth flows, settings, onboarding, data tables, command palettes, modals, toasts. Register `product`: density and glance-speed over marketing polish, no hero/CTA-tricks, every data surface covers empty/loading/error explicitly, tables and dataviz follow preattentive-processing rules.
development
Marketing sites, landing pages, campaign pages — register `brand` (design IS the product). Structure comes from the register's POV + a macrostructure pick, never from copying an inspiration site's section flow. Hero discipline, deviated section order, asymmetric grids, and a silhouette lookalike-test gate before ship.
development
Token-strategy core — OKLCH color rules, neutral tinting, accent-commitment levels, type scale, 8pt spacing grid, touch targets, and the canonical output format of design-system.md (the file the harness gates on). This is routing step 1 of design-method/SKILL.md — read it before design-web/design-webapp/design-ios/design-android, before picking or auditing a single color/type/spacing value.