Covers production env-var handling (read process.env per request, never at module scope — Cloudflare injects env at request time so module-level reads are undefined on the edge), plugin ordering (cloudflare() before tanstackStart()), matching start scripts to the build output, React 19 pinning for Bun, and prerendering limits (dynamic $id routes, layout _ routes, and component-less routes are skipped unless linked via crawlLinks).
Includes full vite.config.ts templates per adapter and a complete Cloudflare Workers deploy template, plus a production checklist.
Do NOT use this skill for app data/auth logic (see start-routing-data / start-auth) or for generic Vite config unrelated to Start.
TanStack Start — Deployment
Agent Workflow (MANDATORY)
Before ANY implementation, spawn in parallel:
- fuse-ai-pilot:explore-codebase — read
vite.config.ts,package.jsonscripts, existing adapter/wrangler config - fuse-ai-pilot:research-expert — verify adapter setup via Context7
/websites/tanstack_start_framework_react - mcp__context7__query-docs — confirm
tanstackStartplugin + adapter options for the target host
After implementation, run fuse-ai-pilot:sniper.
Overview
| Target | Setup |
|---|---|
| Cloudflare Workers ⭐ | @cloudflare/vite-plugin (viteEnvironment: { name: 'ssr' }) + wrangler.jsonc |
| Netlify ⭐ | @netlify/vite-plugin-tanstack-start |
| Vercel / Node / Docker / Bun / Railway | Nitro layer (nitro/vite), .output/server/index.mjs |
| Static | tanstackStart({ prerender: { routes, crawlLinks } }) |
Start builds with Vite (or Rsbuild). Most hosts go through Nitro, an agnostic deploy layer; Cloudflare and Netlify have dedicated Vite plugins.
Critical Rules
- Read env per request —
process.env.Xinside handlers, NEVER at module scope; Cloudflare injects env at request time, so module reads areundefinedon the edge. - Cloudflare plugin order —
cloudflare({ viteEnvironment: { name: 'ssr' } })beforetanstackStart()in the plugins array. - Match
startto the build — Nitro output starts withnode .output/server/index.mjs; Cloudflare useswrangler deploy(nonodestart). - React 19 for Bun — pin
react/react-domto>= 19when deploying on Bun. - Prerender excludes dynamics — param routes (
$id), layout routes (_), and component-less routes are skipped unless linked withcrawlLinks.
Architecture
vite.config.ts # tanstackStart() + host adapter (cloudflare / netlify / nitro)
wrangler.jsonc # Cloudflare only — main: @tanstack/react-start/server-entry
package.json # scripts differ per target (deploy vs start)
.output/server/ # Nitro build output (Node/Vercel/Bun/Railway)
→ See vite-config-adapters.md for every adapter config
Reference Guide
Concepts
| Topic | Reference | Load when |
|---|---|---|
| Build & adapters | build-and-adapters.md | Choosing/configuring a host (Nitro, Vercel, Node, Bun, Railway, Netlify) |
| Cloudflare | cloudflare.md | Deploying to Cloudflare Workers |
| Prerendering | prerendering.md | Generating static HTML at build time |
| Env & checklist | env-and-checklist.md | Production env vars + pre-deploy checklist |
Templates
| Template | When to Use |
|---|---|
| vite-config-adapters.md | Full vite.config.ts per target + prerender |
| cloudflare-deploy.md | Complete Cloudflare Workers setup |
Best Practices
DO
- Confirm the official partner list (Cloudflare, Netlify, Railway) before hand-rolling config
- Keep secrets in the host's env store; read them per request
- Prerender marketing/blog routes; keep dynamic/auth routes SSR
DON'T
- Read
process.envat import time (edge = undefined; bundle leak) - Mix a Nitro
nodestart script with a Cloudflare build - Expect
$paramroutes to prerender withoutcrawlLinks