name: tanstack-start-rsc
description: >-
Implement, review, debug, and refactor TanStack Start React Server
Components in React 19 apps. Use when tasks mention
@tanstack/react-start/rsc, renderServerComponent,
createCompositeComponent, CompositeComponent,
renderToReadableStream, createFromReadableStream, createFromFetch,
Composite Components, React Flight streams, loader or query owned
RSC caching, router.invalidate, structuralSharing: false,
selective SSR, stale names like renderRsc or .validator, or
migration from Next App Router RSC patterns. Do not use for
generic SSR or non-TanStack RSC frameworks except brief
comparison.
TanStack Start React Server Components
Treat TanStack Start RSCs as fetchable React Flight payloads, not as a framework-owned server tree. Start from data ownership and cache ownership, then choose the smallest RSC primitive that fits.
When this skill is active
- Inspect
vite.config.* for tanstackStart({ rsc: { enabled: true } }), rsc(), and viteReact(). - Inspect route files for
loader, loaderDeps, staleTime, ssr, and errorComponent. - Inspect server boundaries:
createServerFn, createServerOnlyFn, .server.*, and imports from @tanstack/react-start/server. - Identify the cache owner: Router loader cache, TanStack Query, or HTTP/server cache.
- Identify the refresh path:
router.invalidate(), invalidateQueries, refetchQueries, or GET cache headers.
Hard invariants
- Route loaders are isomorphic. Do not put DB access, secrets, or Node-only APIs directly in a loader. If the loader itself must use browser APIs, make that route
ssr: false. renderServerComponent(...) returns a renderable fragment. It does not support slots.createCompositeComponent(...) is for server-rendered UI that must accept client-provided children, render props, or component props.- Query-cached RSC values require
structuralSharing: false. - Slot payloads are opaque on the server. Do not inspect, map, or clone
props.children. - Render-prop and component-slot arguments must stay Flight-serializable.
- Current server function validation API is
.inputValidator(...). Older snippets may still show .validator(...); normalize them. - TanStack custom serialization does not apply inside RSCs yet. Stay inside native Flight-supported values.
1) Transport / composition primitive
- No client slots needed ->
renderServerComponent - Client interactivity must be inserted inside server-rendered markup ->
createCompositeComponent + <CompositeComponent src={...} /> - Need custom Flight streaming, API routes, or non-standard transport ->
renderToReadableStream, createFromReadableStream, createFromFetch
2) Cache owner
- Route-shaped data keyed by pathname, params, or search -> Router cache
- Independent key space, background refetch, or non-route ownership -> TanStack Query
- Cross-request reuse on server or CDN -> GET
createServerFn + response cache headers and/or external server cache
3) Refresh owner
- Router-owned RSC ->
router.invalidate() - Query-owned RSC ->
queryClient.invalidateQueries(...) or refetchQueries(...) - Mixed Router + Query -> invalidate both deliberately; do not assume one refreshes the other
Pattern chooser
- Simple server fragment in a route loader ->
renderServerComponent - Interactive slot inside server markup ->
createCompositeComponent - Route component needs browser APIs but loader can still prefetch on the server ->
ssr: 'data-only' - Loader itself needs browser APIs ->
ssr: false - Route cache key must include search params ->
loaderDeps - Query-managed RSC ->
useSuspenseQuery + SSR ensureQueryData - Multiple independent RSCs -> separate server functions +
Promise.all - Multiple RSCs sharing data or invalidating together -> one server function returning many renderables or sources
- Need isolated widget failures or staggered reveal -> return promises from the loader and resolve with
use() inside Suspense
Slot choice
children: free-form composition, no server-to-client data flow- render props: the server must pass serializable data into client-rendered UI
- component props: reusable client slot with a stable typed prop surface
- If you are about to use
Children.map, cloneElement, or inspect children on the server, stop and convert it to a render prop
Review / refactor checklist
- Is the chosen primitive the smallest one that fits?
- Does the loader own the RSC, or should Query own it?
- Are route cache keys complete (
params + minimal loaderDeps)? - Is invalidation hitting the real cache owner?
- Are query options using
structuralSharing: false for any RSC value? - Are mutations explicit
createServerFn({ method: 'POST' }) calls instead of hidden server actions? - Are server-only imports kept inside server functions or server-only boundaries?
- Are examples using current names (
renderServerComponent, .inputValidator) instead of stale ones?
Debug fast
- Setup, exports, or stale docs mismatch ->
docs/current-api-notes.md - Composite Component design or slot bug ->
docs/composite-components.md - Stale data, refetching, loader keys, Query vs Router ownership, or SSR mode ->
docs/caching-refresh-ssr.md - Review, refactor, import leaks, error boundaries, or serialization bugs ->
docs/debugging-review.md - Architecture and Next/App Router translation ->
docs/architecture.md
Copy-paste patterns
examples/01-renderable-route-loader.tsxexamples/02-composite-slots.tsxexamples/03-query-owned-rsc.tsxexamples/04-selective-ssr-data-only.tsxexamples/05-ssr-false-browser-loader.tsxexamples/06-low-level-flight-api-route.tsx
Default implementation sequence
- Keep server-only work inside
createServerFn or createServerOnlyFn - Return an RSC from the server function
- Consume it through the route loader unless Query has a clear ownership advantage
- Add the smallest cache policy that satisfies freshness requirements
- Wire invalidation exactly once at the real cache owner
- Escalate to Composite Components only when the client must fill slots
- Escalate to low-level Flight APIs only when high-level helpers cannot express the transport