You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: src/blog/tanstack-start-rsc.md
+67-8Lines changed: 67 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -6,9 +6,11 @@ authors:
6
6
- Manuel Schiller
7
7
---
8
8
9
+

10
+
9
11
React Server Components are a genuine leap for React. They reduce bundle size, stream UI as it resolves, and move work off the client.
10
12
11
-
But the current model comes with a tradeoff: **the server owns the component tree**.
13
+
But the way RSCs have been implemented so far comes with a tradeoff: **the server owns the component tree**.
12
14
Your client code opts into interactivity with `'use client'`. **Composition flows one direction**—server decides, client receives. The React model you know—props, context, bidirectional composition—gets fragmented across environments.
13
15
14
16
What if it didn't have to?
@@ -19,9 +21,25 @@ That's what we built in **TanStack Start**.
19
21
20
22
---
21
23
24
+
## Why RSCs Matter
25
+
26
+
Before diving into the model, it's worth understanding when server components shine:
27
+
28
+
-**Heavy dependencies stay on the server.** Markdown parsers, syntax highlighters, date formatting libraries—these can add hundreds of KB to your bundle. With RSCs, that code runs on the server and only the rendered output ships to the client.
29
+
30
+
-**Colocated data fetching.** TanStack Router already eliminates waterfalls by parallelizing route loaders. RSCs offer a different ergonomic: awaiting data directly in the component that renders it, which can be convenient for static or slow-changing content.
31
+
32
+
-**Sensitive logic stays secure.** API keys, database queries, business logic—none of it reaches the client bundle.
33
+
34
+
-**Streaming for perceived performance.** Instead of waiting for all data before showing anything, RSCs stream UI progressively. Users see content immediately while slower parts load in the background.
35
+
36
+
RSCs aren't about replacing client interactivity—they're about choosing where work happens. **The question is: who controls that choice?**
37
+
38
+
---
39
+
22
40
## The One-Way Problem
23
41
24
-
In the traditional RSC model, the server is the only place that decides what interactive elements to render.
42
+
In existing RSC implementations, the server is the only place that decides what interactive elements to render.
25
43
26
44
Need a `<Link>` with prefetching? **Server** renders it with `'use client'`.
27
45
Need a dashboard widget? Server renders the boundary, marks it client. **The server is always the decision-maker. The client is always the recipient.**
@@ -39,7 +57,7 @@ In the traditional model, you'd create a new client component, a new file, a new
39
57
40
58
**Server components can render interactive elements directly**—`<Link>`, `<Button>`, anything marked `'use client'`. That part works the same.
41
59
42
-
But they can also **declare slots**—regions where the client decides what to render. Not by creating new components or files, but through plain props: children, render functions, whatever pattern you already use.
60
+
But they can also **declare slots**—regions where the client decides what to render. Not by creating new components or files, but through plain props: children, render functions, whatever pattern you already use. These aren't special APIs—`renderActions` in the example below is just a prop name we chose. You can name your render props anything you want.
Here's the shift: in TanStack Start, **server components aren't a paradigm**. They're a _primitive_—a serialization format that flows through your existing architecture.
110
130
111
-
When `createServerFn` returns a server component, that component is a **stream**. Streams are universal. They work with:
131
+
When `createServerFn` returns a server component, that component is a **stream**. This means you don't have to wait for the entire component to finish rendering before sending HTML to the client. Parts of the UI can render immediately while slower async work (database queries, API calls) resolves in the background. The client sees a Suspense fallback, then the final content streams in when ready—no full page reload, no blocking.
132
+
133
+
This works for SSR (streaming HTML during initial page load) and for client-side fetches (streaming RSC payloads during navigation or data refetches).
134
+
135
+
Streams are universal. They work with:
112
136
113
137
-**TanStack Router** → Load server components in route loaders, stream them during navigation
114
138
-**TanStack Query** → Cache server components, refetch in the background, deduplicate requests
@@ -155,6 +179,8 @@ Every property access and function call is tracked:
155
179
-`props.children` → serialized as a slot placeholder
156
180
-`props.renderActions({ postId, authorId })` → serialized with the arguments attached
157
181
182
+
You can destructure props normally—`({ children, renderActions })` works just as well as `props.children`. The proxy handles both patterns.
183
+
158
184
**Over the wire, it's a React element stream** with embedded placeholders. On the client:
159
185
160
186
1. The stream decodes into a React element tree
@@ -235,7 +261,7 @@ Because it's not about client or server.
235
261
236
262
This is the first experimental release of RSCs in TanStack Start. A few things to know:
237
263
238
-
**React's Flight serializer** — This release uses React's native RSC Flight protocol for serialization. That means TanStack Start's usual Seroval-based serialization isn't available within server components for now. Standard JavaScript primitives, Dates, and React elements serialize fine. Custom Seroval plugins and extended types will come in a future release as we unify the serialization layers.
264
+
**React's Flight serializer** — This release uses React's native RSC Flight protocol for serialization. That means TanStack Start's usual serialization features aren't available within server components for now. Standard JavaScript primitives, Dates, and React elements serialize fine. Custom serialization plugins and extended types will come in a future release as we unify the serialization layers.
239
265
240
266
**API surface** — The `createServerComponent` API and slot patterns shown here are stable in design but may see refinements based on feedback. We're shipping early to learn from real usage.
241
267
@@ -267,9 +293,42 @@ No. RSCs are entirely opt-in. You can build fully client-side SPAs with TanStack
267
293
268
294
TanStack Start's RSC implementation builds on React's Flight protocol and works with React 19. Server Actions are a separate primitive—`createServerFn` serves a similar purpose but integrates with TanStack's middleware, validation, and caching model. We're watching the Server Actions API and will align where it makes sense.
269
295
270
-
### When will Seroval serialization work inside RSCs?
296
+
### When will TanStack Start's full serialization work inside RSCs?
297
+
298
+
It's on the roadmap. The current release uses React's Flight serializer directly, which handles the core use cases. Unifying with TanStack Start's serializer for custom types, extended serialization, and tighter TanStack DB integration is planned for a future release.
299
+
300
+
### Can I define my component outside of `createServerComponent`?
301
+
302
+
Yes. `createServerComponent` initiates the RSC stream generation, but your component can be defined separately and invoked inside:
303
+
304
+
```tsx
305
+
function PostArticle({ post, children, renderActions }) {
306
+
return (
307
+
<article>
308
+
<h1>{post.title}</h1>
309
+
{renderActions?.({ postId: post.id })}
310
+
{children}
311
+
</article>
312
+
)
313
+
}
314
+
315
+
const getPost =createServerFn().handler(async ({ data }) => {
316
+
const post =awaitdb.posts.get(data.postId)
317
+
returncreateServerComponent((props) => (
318
+
<PostArticlepost={post}{...props} />
319
+
))
320
+
})
321
+
```
322
+
323
+
### Can I return raw JSX instead of using `createServerComponent`?
324
+
325
+
Not currently. Server functions that return UI must wrap it in `createServerComponent`. This is what enables the streaming, slot handling, and client rehydration. Plain JSX returned from a server function won't have the RSC serialization behavior.
326
+
327
+
### Do `cloneElement` and React Context work with server component children?
328
+
329
+
**`cloneElement`**: This won't work as you might expect. When children are passed from client to server, they're serialized as slot placeholders—not actual React elements the server can manipulate. The server can't inspect or clone client-provided children.
271
330
272
-
It's on the roadmap. The current release uses React's Flight serializer directly, which handles the core use cases. Unifying with Seroval for custom types, extended serialization, and tighter TanStack DB integration is planned for a future release.
331
+
**React Context**: Context providers rendered by the server component _will_ wrap client children. If your server component renders `<ThemeProvider value="dark">{children}</ThemeProvider>`, the client children can consume that context. However, the context must be defined in a way that works across the server/client boundary (typically with `'use client'` on the provider component).
0 commit comments