TanStack Start (React) — RC-Ready Playbook
Full-stack React on TanStack Router with per-route SSR/CSR, file-based routing, server functions, and first-class Cloudflare Workers support.
Use this skill when
- Building a greenfield React app that needs route-level SSR/CSR/SSG switches.
- Migrating from Next.js/React Router while keeping file-based routing + API routes.
- Shipping to edge runtimes (Workers) with typed server functions and bindings.
- You want predictable routing with type-safe params/search + built-in preloading.
What’s inside
- References: quickstart/layout, rendering modes, server functions, Cloudflare hosting, execution/auth, plus new routing/data/navigation/devtools guides.
- Script:
scripts/bootstrap-cloudflare-start.sh <app> scaffolds Start + Workers + binding types.
- Troubleshooting: hydration, API routing, bindings, navigation/preloading failures.
Quick Start (React)
npm create @tanstack/start@latest my-app
cd my-app
npm run dev
Manual installs (all bundle targets are supported): add @tanstack/react-router + @tanstack/react-start with your bundler plugin (vite, webpack, or esbuild) per the official install guides.
Core layout reminder
app/routes/** file-based routes → router tree, automatic code-splitting + data preloading.
app/entry.client.tsx hydrates <StartClient />; app/entry.server.tsx wraps createServerEntry.
app/config.ts or app/start.ts sets defaultSsr, spaMode, middleware, and context.
Routing + Data Best Practices
- Type-safe params & search:
createFileRoute() infers path params; add validateSearch (zod) to parse and coerce search params.
- Route matching order is deterministic (index → static → dynamic → splat); rely on this when adding catch-alls.
- Loaders run once per location change; return plain data, throw
redirect()/notFound() for control flow.
- Data mutations: colocate
action/server functions; keep loaders read-only and invalidate via router.invalidate() after mutation.
- TanStack Query bridge: create a
QueryClient in router context and ensureQueryData inside loaders to dedupe fetches.
- Deferred/external data: stream partial data or read from external loaders; prefer suspense-friendly responses.
- Head management: set
head per route for <title>/meta; derive from loader data to keep SEO consistent.
- Not-found/auth: throw
notFound() or redirect() in loaders/middleware; use error boundaries for UX.
Example route (typed search + data-only SSR):
// app/routes/posts.$postId.tsx
import { createFileRoute, redirect } from '@tanstack/react-router'
import { z } from 'zod'
export const Route = createFileRoute('/posts/$postId')({
validateSearch: z.object({ preview: z.boolean().optional() }),
ssr: 'data-only',
loader: async ({ params, search, context }) => {
const post = await context.queryClient.ensureQueryData(['post', params.postId], () =>
fetch(`/api/posts/${params.postId}?preview=${!!search.preview}`).then(r => r.json())
)
if (!post.published && !search.preview) throw redirect({ to: '/drafts' })
return { post }
},
})
Navigation, Preloading, and UX
- Link prefetch defaults:
<Link preload="intent"> (hover/focus) preloads route data/code; use preload="render" for above-the-fold routes.
- Programmatic preloading:
router.preloadRoute({ to, search }) to warm caches before navigation (e.g., on visibility).
- Route masking: keep canonical URLs while showing user-friendly masks (e.g.,
/products?slug=abc masked as /p/abc).
- Navigation blocking: protect unsaved forms with
router.navigate({ to, replace, from }) blockers or useBlocker.
- Scroll restoration: enable
scrollRestoration to restore positions on back/forward; customize per route when using long lists.
- Search param serialization: customize parse/stringify to keep numbers/dates stable and avoid stringified booleans.
Rendering & Performance
- Per-route SSR: set
ssr: true | false | 'data-only' on routes; defaultSsr config sets the baseline.
- Code-splitting: file-based routes auto-split; add
lazy/load for manual chunks on code-based routes.
- Preloading strategy: pair
preload="intent" links with defaultPreloadStaleTime to avoid over-fetching.
- Render optimizations: keep loaders pure, memoize heavy components, and use
pendingComponent for CSR routes to avoid layout shift.
Devtools, Linting, and LLM Support
- Add
<RouterDevtools /> during development to inspect matches, loader states, and preloading.
- Enable the ESLint plugin
@tanstack/eslint-plugin-router with the recommended config to enforce inference-sensitive property order (e.g., beforeLoad before loader).
- LLM-aware routing: the Router exposes structured route metadata to LLM agents; keep descriptions concise in
Route meta for better AI navigation.
Deployment Notes (Cloudflare-friendly)
- Keep
cloudflare({ viteEnvironment: { name: 'ssr' } }) first in Vite plugins so bindings reach server entry.
- Regenerate bindings after changes:
npm run cf-typegen.
- For static-heavy sites, enable prerender to ship HTML to Workers Assets/Pages; exclude param routes or add explicit
pages.
Ship Checklist
1---2name: tanstack-start-53description: TanStack Start (RC) full-stack React with server functions, SSR, Cloudflare Workers. Use for Next.js migration, edge rendering, or encountering hydration, auth, data pattern errors.4license: MIT5---6
7# TanStack Start (React) — RC-Ready Playbook
8
9Full-stack React on TanStack Router with per-route SSR/CSR, file-based routing, server functions, and first-class Cloudflare Workers support.
10
11## Use this skill when
12- Building a greenfield React app that needs route-level SSR/CSR/SSG switches.
13- Migrating from Next.js/React Router while keeping file-based routing + API routes.
14- Shipping to edge runtimes (Workers) with typed server functions and bindings.
15- You want predictable routing with type-safe params/search + built-in preloading.
16
17## What’s inside
18- **References**: quickstart/layout, rendering modes, server functions, Cloudflare hosting, execution/auth, plus new routing/data/navigation/devtools guides.
19- **Script**: `scripts/bootstrap-cloudflare-start.sh <app>` scaffolds Start + Workers + binding types.
20- **Troubleshooting**: hydration, API routing, bindings, navigation/preloading failures.
21
22---
23
24## Quick Start (React)
25```bash
26npm create @tanstack/start@latest my-app
27cd my-app
28npm run dev
29```
30Manual installs (all bundle targets are supported): add `@tanstack/react-router` + `@tanstack/react-start` with your bundler plugin (`vite`, `webpack`, or `esbuild`) per the official install guides.
31
32### Core layout reminder
33- `app/routes/**` file-based routes → router tree, automatic code-splitting + data preloading.
34- `app/entry.client.tsx` hydrates `<StartClient />`; `app/entry.server.tsx` wraps `createServerEntry`.
35- `app/config.ts` or `app/start.ts` sets `defaultSsr`, `spaMode`, middleware, and context.
36
37---
38
39## Routing + Data Best Practices
40
41- **Type-safe params & search**: `createFileRoute()` infers path params; add `validateSearch` (zod) to parse and coerce search params.
42- **Route matching order is deterministic** (index → static → dynamic → splat); rely on this when adding catch-alls.
43- **Loaders run once per location change**; return plain data, throw `redirect()`/`notFound()` for control flow.
44- **Data mutations**: colocate `action`/server functions; keep loaders read-only and invalidate via `router.invalidate()` after mutation.
45- **TanStack Query bridge**: create a `QueryClient` in router context and `ensureQueryData` inside loaders to dedupe fetches.
46- **Deferred/external data**: stream partial data or read from external loaders; prefer suspense-friendly responses.
47- **Head management**: set `head` per route for `<title>`/meta; derive from loader data to keep SEO consistent.
48- **Not-found/auth**: throw `notFound()` or `redirect()` in loaders/middleware; use error boundaries for UX.
49
50Example route (typed search + data-only SSR):
51```ts
52// app/routes/posts.$postId.tsx
53import { createFileRoute, redirect } from '@tanstack/react-router'
54import { z } from 'zod'
55
56export const Route = createFileRoute('/posts/$postId')({
57 validateSearch: z.object({ preview: z.boolean().optional() }),
58 ssr: 'data-only',
59 loader: async ({ params, search, context }) => {
60 const post = await context.queryClient.ensureQueryData(['post', params.postId], () =>
61 fetch(`/api/posts/${params.postId}?preview=${!!search.preview}`).then(r => r.json())
62 )
63 if (!post.published && !search.preview) throw redirect({ to: '/drafts' })
64 return { post }
65 },
66})
67```
68
69---
70
71## Navigation, Preloading, and UX
72
73- **Link prefetch defaults**: `<Link preload="intent">` (hover/focus) preloads route data/code; use `preload="render"` for above-the-fold routes.
74- **Programmatic preloading**: `router.preloadRoute({ to, search })` to warm caches before navigation (e.g., on visibility).
75- **Route masking**: keep canonical URLs while showing user-friendly masks (e.g., `/products?slug=abc` masked as `/p/abc`).
76- **Navigation blocking**: protect unsaved forms with `router.navigate({ to, replace, from })` blockers or `useBlocker`.
77- **Scroll restoration**: enable `scrollRestoration` to restore positions on back/forward; customize per route when using long lists.
78- **Search param serialization**: customize parse/stringify to keep numbers/dates stable and avoid stringified booleans.
79
80---
81
82## Rendering & Performance
83
84- **Per-route SSR**: set `ssr: true | false | 'data-only'` on routes; `defaultSsr` config sets the baseline.
85- **Code-splitting**: file-based routes auto-split; add `lazy`/`load` for manual chunks on code-based routes.
86- **Preloading strategy**: pair `preload="intent"` links with `defaultPreloadStaleTime` to avoid over-fetching.
87- **Render optimizations**: keep loaders pure, memoize heavy components, and use `pendingComponent` for CSR routes to avoid layout shift.
88
89---
90
91## Devtools, Linting, and LLM Support
92
93- Add `<RouterDevtools />` during development to inspect matches, loader states, and preloading.
94- Enable the ESLint plugin `@tanstack/eslint-plugin-router` with the recommended config to enforce inference-sensitive property order (e.g., `beforeLoad` before `loader`).
95- LLM-aware routing: the Router exposes structured route metadata to LLM agents; keep descriptions concise in `Route` meta for better AI navigation.
96
97---
98
99## Deployment Notes (Cloudflare-friendly)
100
101- Keep `cloudflare({ viteEnvironment: { name: 'ssr' } })` first in Vite plugins so bindings reach server entry.
102- Regenerate bindings after changes: `npm run cf-typegen`.
103- For static-heavy sites, enable prerender to ship HTML to Workers Assets/Pages; exclude param routes or add explicit `pages`.
104
105---
106
107## Ship Checklist
108- [ ] Routes load without hydration warnings (prefer `ssr: 'data-only'` for non-deterministic UI).
109- [ ] Search params validated with `validateSearch` and custom serializer where needed.
110- [ ] Link preloading configured for high-traffic routes; blockers added for unsaved forms.
111- [ ] ESLint plugin enabled (`create-route-property-order` rule) and `npm run check` passes.
112- [ ] Devtools verified locally; `router.matches` state looks correct.
113- [ ] Cloudflare bindings typed (`cf-typegen`) and streaming tested via `curl -N`.