Skip to content

Commit 6885965

Browse files
committed
docs: polish react server components post
1 parent 799bc12 commit 6885965

2 files changed

Lines changed: 15 additions & 20 deletions

File tree

src/blog/react-server-components.md

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -31,25 +31,19 @@ They are a _very powerful primitive_.
3131

3232
## The RSC Status Quo
3333

34-
**However**, we do not see RSCs on their own as some kind of silver bullet or magic hammer meant to be applied to every corner of software engineering, let alone an entire framework.
34+
Most people now think of RSCs in a server-first way: the server owns the tree, `'use client'` marks the interactive parts, and the framework conventions decide how the whole thing fits together.
3535

36-
In most current server-composed RSC frameworks, the server owns the component tree, you opt into interactivity with `'use client'`, and the server decides the final shape of the UI. Client components can render and hydrate on the client, but the server still owns the destiny.
36+
That model can be compelling. It makes streaming, server rendering, and colocated server-side work feel built in from the start.
3737

38-
Likewise, the expectation in existing frameworks is that RSCs can only be created and controlled via implicit conventions, deeply integrated into the framework itself.
38+
But it also turns RSCs from a useful primitive into the thing your whole app has to orbit. The framework ends up owning how RSCs are created, where they render, how interactive boundaries are defined, and how UI gets recomposed when data or user actions change.
3939

40-
This model only works if you trust that both your framework and server can know everything necessary to decide the final shape of the UI, and it only continues working if you are willing to keep going back to the server any time you need to rebuild and reconcile new UI in response to new data and user actions.
41-
42-
On paper, this server-first, "RSC-native" experience looks simple and elegant, but in practice it can make the lifecycle of your application artificially constrained by both the server and the framework, and tightly couple you to the conventions that implement that lifecycle.
43-
44-
Just as it's possible to use a hammer to build an entire home, it's possible to build an entire full-stack framework with RSCs at the heart, but at what cost?
45-
46-
At TanStack, we know we deserve much better than what has historically been a tightly coupled, monolithic, black-box API.
40+
That is the part we kept getting hung up on. We do not think you should have to buy into that whole model up front just to get value out of RSCs.
4741

4842
## A Different RSC Model
4943

5044
What if you could use RSCs as granularly as you could fetch JSON on the client? In fact, what if the client decided how server-rendered UI gets fetched, cached, and composed in the first place?
5145

52-
In TanStack Start, the core idea is that RSCs are **just streams of data** that you can fetch, cache, and render on your terms at any time on the client instead of a server-owned component tree. This extremely simple change alone makes them **infinitely more composable without changing anything fundamental about how they work** or how a framework can use them.
46+
In TanStack Start, the core idea is that RSCs are **just streams of data** that you can fetch, cache, and render on your terms at any time on the client instead of a server-owned component tree. That one shift makes them far more composable without changing anything fundamental about how they work.
5347

5448
With TanStack Start, RSCs are just React Flight streams. That sounds almost too obvious to say out loud, but that's exactly the point. We did not want them wrapped in a black-box convention with special rules, APIs, and network effects that change everything about the framework.
5549

@@ -166,7 +160,7 @@ export const Route = createFileRoute('/hello')({
166160

167161
> Navigate from `/posts/abc` to `/posts/xyz` and the loader runs again. Navigate back to `/posts/abc` and Router can serve the cached result instantly. That snappy back-button experience falls out of the same loader caching model you are already using.
168162
169-
With Start, **RSCs are not a separate universe, "mode," or "router"**. They fit naturally into existing data workflows and tools you've been working with.
163+
With Start, RSCs fit into the same data workflows you already use.
170164

171165
## Security: One-Way Data Flow
172166

@@ -176,7 +170,7 @@ We intentionally do not support `'use server'` actions, both because of existing
176170

177171
TanStack Start requires explicit RPCs via `createServerFn`. The client-server boundary is deliberate, with hardened serialization, validation, and middleware semantics that encourage treating all user input as untrusted by default.
178172

179-
The win is reduced attack surface through explicit communication patterns, not magic. Still treat server functions like any API surface: authenticate, validate, and keep dependencies patched.
173+
This keeps the attack surface smaller because the communication pattern is explicit. Still treat server functions like any API surface: authenticate, validate, and keep dependencies patched.
180174

181175
## The Full Spectrum
182176

@@ -186,7 +180,7 @@ With RSCs as primitives, TanStack Start covers every frontend use case. And we m
186180
No server components at all. Client-first, SPA-style. RSCs are an optimization you add when helpful, not a paradigm you're forced to build around. This is where most "apps" already live today.
187181

188182
- **Hybrid**
189-
Server components for static shells, data-heavy regions, or SEO-critical content, with client components where interactivity matters. Mixed "app" and "site" projects (product + marketing) are a clear win here. RSCs will come in handy for "site" stuff way more often.
183+
Server components for static shells, data-heavy regions, or SEO-critical content, with client components where interactivity matters. Mixed "app" and "site" projects (product + marketing) fit this especially well. In practice, this tends to help the "site" side more often than the "app" side.
190184

191185
- **Mostly Static**
192186
Predominantly static content, parsed and rendered server-side as RSCs, but still a powerful and hydrated SPA with bits of client interactivity sprinkled in where needed (e.g. comments, search, dynamic widgets). Think blogs, docs, marketing pages.
@@ -247,7 +241,7 @@ That is the part that feels genuinely new to us. Most RSC systems let the server
247241
A Composite Component can render server UI while exposing **slots** for client content. Slots use plain React patterns you already know:
248242

249243
- `children`
250-
- render props (like `renderActions`)
244+
- render props (like `renderPostActions`)
251245

252246
Because the client owns the component tree, the components you pass into slots are regular client components. No `'use client'` directive required. The server positions them as opaque placeholders but can't inspect, clone, or transform them. That is the point: the server can ask for "something goes here" without needing to know what that something is.
253247

@@ -262,7 +256,7 @@ const getPost = createServerFn().handler(async ({ data }) => {
262256
const src = await createCompositeComponent(
263257
(props: {
264258
children?: React.ReactNode
265-
renderActions?: (data: {
259+
renderPostActions?: (data: {
266260
postId: string
267261
authorId: string
268262
}) => React.ReactNode
@@ -278,7 +272,7 @@ const getPost = createServerFn().handler(async ({ data }) => {
278272

279273
{/* Slot: server requests client UI here */}
280274
<footer>
281-
{props.renderActions?.({ postId: post.id, authorId: post.authorId })}
275+
{props.renderPostActions?.({ postId: post.id, authorId: post.authorId })}
282276
</footer>
283277

284278
{/* Slot: client fills this with children */}
@@ -305,7 +299,7 @@ function PostPage({ postId }) {
305299
return (
306300
<CompositeComponent
307301
src={data.src}
308-
renderActions={({ postId, authorId }) => (
302+
renderPostActions={({ postId, authorId }) => (
309303
// Full client interactivity: hooks, state, context
310304
<PostActions postId={postId} authorId={authorId} />
311305
)}
@@ -318,9 +312,11 @@ function PostPage({ postId }) {
318312

319313
The server renders the `<Link>` directly and leaves join points for the client:
320314

321-
- A render prop slot for `<PostActions>` with server-provided arguments
315+
- A `renderPostActions` slot for `<PostActions>` with server-provided arguments
322316
- A `children` slot for `<Comments>`
323317

318+
Slot names are just props. `renderPostActions` is example code, not special API syntax.
319+
324320
Since a Composite Component is still data, the client can also treat it as a building block:
325321

326322
- Interleave multiple fragments in a new tree

src/components/landing/LandingCodeExampleCard.server.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import * as React from 'react'
2-
import { Card } from '~/components/Card'
32
import { CodeBlock } from '~/components/markdown/CodeBlock.server'
43
import { Tabs, type TabDefinition } from '~/components/markdown/Tabs'
54
import type { Framework } from '~/libraries'

0 commit comments

Comments
 (0)