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
+36-14Lines changed: 36 additions & 14 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -11,11 +11,11 @@ authors:
11
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.
12
12
13
13
But the way RSCs have been implemented so far comes with a tradeoff: **the server owns the component tree**.
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.
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, client-led composition—gets fragmented across environments.
15
15
16
16
What if it didn't have to?
17
17
18
-
What if RSCs were actually components? **Fetchable. Cacheable. Composable in both directions.** Primitives that flow through your router, your cache, your data layer—without special directives or framework lock-in.
18
+
What if RSCs were actually components? **Fetchable. Cacheable. Composable by the client.** Primitives that flow through your router, your cache, your data layer—without special directives or framework lock-in.
19
19
20
20
That's what we built in **TanStack Start**.
21
21
@@ -51,13 +51,24 @@ In the traditional model, you'd create a new client component, a new file, a new
51
51
52
52
---
53
53
54
-
## Bidirectional Composition
54
+
## Composite Components
55
55
56
-
**TanStack Start flips the constraint.**
56
+
**TanStack Start treats RSC output as a composable UI value.**
57
57
58
-
**Server components can render interactive elements directly**—`<Link>`, `<Button>`, anything marked `'use client'`. That part works the same.
58
+
A **Composite Component** is a server-produced React component that the client can fetch, cache, stream, and then **assemble into its own UI tree**. It’s not “the server owns the tree and the client hydrates what it gets”. It’s the server shipping UI _pieces_ that remain composable at the client.
59
59
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.
60
+
Composite Components give you two levels of composition:
61
+
62
+
### 1) Intra-component slotting (composition inside one component)
63
+
64
+
A Composite Component can render real server UI, _and_ expose **slots** using plain React patterns:
65
+
66
+
-`children`
67
+
- render props (like `renderActions`)
68
+
69
+
These aren’t framework-specific APIs. They’re just props.
70
+
71
+
The important nuance: client-provided UI crosses the boundary as **opaque slot placeholders**. The server can position those placeholders (because it owns the frame), but it can’t inspect, clone, or transform client subtrees. That’s what keeps the model predictable.
@@ -117,10 +128,21 @@ function PostPage({ postId }) {
117
128
}
118
129
```
119
130
120
-
**The server rendered the `<Link>` directly.** It also passed `postId` and `authorId` through the slot so the client could render `<PostActions>` with that data. And it left children open for the client to fill with `<Comments>`.
131
+
Here the server renders the `<Link>` directly, but leaves **join points** for the client:
132
+
133
+
- a render prop slot for `<PostActions>` (with server-provided arguments)
134
+
- a `children` slot for `<Comments>`
135
+
136
+
### 2) Inter-component composition (composition across many components)
137
+
138
+
Once a Composite Component is just “data you can render”, the client can treat it like a building block:
139
+
140
+
- interleave multiple Composite Components in a brand-new tree
141
+
- wrap them in client providers/layout
142
+
- nest one Composite Component inside another by passing it through slots
143
+
- reorder/swap them based on client state
121
144
122
-
**Both directions. Same component. No new files. No new boundaries.**
123
-
That's inversion of control.
145
+
Same component. Same mental model. No new boundaries for every deferral.
124
146
125
147
---
126
148
@@ -249,7 +271,7 @@ But the traditional model makes a choice: **the server owns the tree, and intera
249
271
250
272
> RSCs are a serialization format—a way to stream React elements from the server. They're a primitive, not a paradigm. Use them when they help. Compose around them when you need control. The client and server are peers, not a hierarchy.
251
273
252
-
The server can render interactive elements directly. It can also defer to the client through slots. **Composition flows both directions.**
274
+
The server can render interactive elements directly. It can also defer to the client through slots. **The client still owns the final composition.**
253
275
You decide the balance, per-component, based on what makes sense.
254
276
255
277
Because it's not about client or server.
@@ -277,7 +299,7 @@ If you hit rough edges, [open an issue](https://github.com/tanstack/router/issue
277
299
278
300
Next.js App Router is server-first: your component tree lives on the server by default, and you opt into client interactivity with `'use client'` boundaries.
279
301
280
-
TanStack Start is **isomorphic-first**: your tree lives wherever makes sense, and RSCs are a primitive you pull in when helpful. The key difference is **bidirectional composition**—server components can defer to the client through slots, not just the other way around.
302
+
TanStack Start is **isomorphic-first**: your tree lives wherever makes sense, and RSCs are a primitive you pull in when helpful. The key difference is **client-led composition**—Composite Components expose slots so the client can assemble the final tree, not just accept a server-owned one.
281
303
282
304
Both approaches support RSCs. They differ in who owns the tree by default and how composition flows.
283
305
@@ -289,7 +311,7 @@ Not directly—TanStack Start is its own framework built on TanStack Router. But
289
311
290
312
No. RSCs are entirely opt-in. You can build fully client-side SPAs with TanStack Start, use traditional SSR without server components, or go fully static. RSCs are one tool in the spectrum, not a requirement.
291
313
292
-
### What about React 19 / `use` / Server Actions?
314
+
### What about React 19 / `use server` / Server Actions?
293
315
294
316
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.
0 commit comments