@tanstack/react-start
Version:
Modern and scalable routing for React applications
85 lines (53 loc) • 3.96 kB
Markdown
TanStack Start treats React Server Components as plain React Flight data:
- create them on the server
- return them from a server function or API route
- decode and render them during SSR or on the client
- cache them with whatever already owns the data flow
That matters because the framework is not asking you to move the whole UI tree to the server. RSCs are opt-in fragments that fit into existing Router and Query workflows.
The fastest way to get TanStack Start RSC wrong is to think “loader = server”.
Use these boundaries instead:
- `createServerFn`: explicit RPC boundary; safe to call from client code because the client gets an RPC stub
- `createServerOnlyFn`: utility that must never run on the client
- route `loader`: orchestration layer that runs on the server for the initial SSR request and in the browser on client navigation unless the route is `ssr: false`
Practical rule: loaders decide _when_ to fetch. Server functions decide _what_ must stay on the server.
Do not over-correct and assume RSCs are a stripped-down universe.
- TanStack Router `Link` works inside server components
- CSS Modules and global CSS imports work inside server components
- `React.cache` works for request-scoped memoization inside server components
Use those directly when they simplify the tree.
Use TanStack Start RSCs when one or more of these are true:
- a region is heavy to render but mostly static once delivered
- a server-only dependency should never enter the client bundle
- data fetching and render logic want to live together
- you want progressively streamed HTML for part of the route
- the result can be cached better as a fragment than as an entire page
Do not force RSCs into highly interactive, state-dense client surfaces unless they materially reduce bundle size or remove awkward client/server sync.
Think in terms of ownership:
- client-owned UI: plain client components, Query, SPA patterns
- route-owned RSC: route loader fetches a fragment and Router cache owns freshness
- query-owned RSC: Query owns the fragment because its lifecycle is not route-shaped
- mixed ownership: the route owns coarse navigation state, Query owns sub-fragments
The wrong smell is accidental double ownership: one RSC is fetched in a loader, then also separately cached in Query, then only one side is invalidated.
A Composite Component is a server-rendered fragment with placeholders the client fills later.
That makes it the replacement for “I need server-rendered markup here, but I still need client interactivity in the middle of it.”
The client can wrap, nest, reorder, and interleave those fragments instead of accepting a single framework-owned server tree.
Use this mapping when someone is thinking in Next terms:
- Next “server-first tree” -> Start “isomorphic-first app with opt-in RSC fragments”
- Next `'use client'` boundary -> Start Composite Component slot boundary
- Next server actions -> Start explicit `createServerFn({ method: 'POST' })`
- Next framework cache semantics -> Start Router cache, Query cache, and HTTP cache you control directly
The useful mental shift is this: in TanStack Start, RSC is a transport and composition primitive, not the center of gravity for the whole app.
- If the fragment never accepts client content, demote it to `renderServerComponent`.
- If a route-scoped RSC never needs independent refetching, keep it in the loader cache.
- If a fragment is reused across routes or refreshed independently, consider Query ownership.
- If several fragments always share data and invalidate together, bundle them in one server function.
- If one slow fragment blocks everything, stop awaiting it in the loader and defer it.