← Journal
June 22, 2026·12 min read·Nasywan

DeepDiveintoReactServerComponents:HowDataFlowsWithoutDoubleHydration

React Server Components completely rethink how UI and data get shipped to the browser. Let's break down the underlying architecture, how the wire protocol works, and why RSC eliminates the double-bundle penalty for heavy dependencies.

ReactRSCServer ComponentsClient ComponentsPerformance
Deep Dive into React Server Components: How Data Flows Without Double Hydration

For nearly a decade, the standard React development model remained relatively unchanged. You built components, bundled them up into JavaScript files, pushed them to a CDN, and sent a shell HTML file to the user. The browser downloaded megabytes of JavaScript, parsed it, executed it, mounted the DOM, and then triggered secondary network requests to fetch JSON data from your API.

Even traditional Server-Side Rendering (SSR) suffered from a major drawback called double hydration. The server rendered HTML so users could see the initial UI quickly, but the browser still had to download the exact same component JavaScript bundle to attach event listeners and verify the DOM tree. That meant paying the bundle cost twice: once on the server, and once over the wire to the client device.

React Server Components (RSC) solve this fundamental bottleneck by introducing a split execution architecture where server components execute exclusively on the server and never ship their source code or third-party dependencies to the client browser.

Server Components vs Client Components

The most common misconception about RSC is thinking that 'use client' marks a component to be rendered only in the browser. In reality, components marked with 'use client' still render on the server during initial SSR. What 'use client' actually defines is an interactivity boundary: it tells the bundler that this component and its children require client-side JavaScript to handle event listeners, state hooks, and effects.

Server components, on the other hand, are the default. They can be async functions, talk directly to databases, read environment secrets, and import heavy dependencies like date-fns, marked, or markdown parsers without adding a single byte to the user's JavaScript download.

1// app/blog/[slug]/page.tsx (Server Component by default)
2import { db } from '@/lib/db';
3import { marked } from 'marked'; // Stays on server, 0 KB client bundle!
4import { BookmarkButton } from './BookmarkButton'; // Client component
5
6export default async function BlogPostPage({ params }: { params: { slug: string } }) {
7 const post = await db.post.findUnique({ where: { slug: params.slug } });
8 if (!post) return <div>Post not found</div>;
9
10 const htmlContent = marked.parse(post.markdown);
11
12 return (
13 <article className="max-w-2xl mx-auto py-8">
14 <div className="flex justify-between items-center mb-4">
15 <h1 className="text-3xl font-bold">{post.title}</h1>
16 <BookmarkButton postId={post.id} />
17 </div>
18 <div dangerouslySetInnerHTML={{ __html: htmlContent }} />
19 </article>
20 );
21}

Understanding the Flight Wire Protocol

So what actually travels between the server and the browser when a server component re-renders? It is not raw HTML, nor is it standard JSON. React uses a specialized streaming data format known colloquially as the Flight protocol.

The wire protocol streams serialized representations of the virtual DOM. It assigns unique IDs to chunks of the tree, encodes native HTML elements, and replaces client components with special reference placeholders pointing to the client bundle chunk that needs to mount in that slot.

1M1:{"id":"./src/BookmarkButton.tsx","chunks":["client1"],"name":""}
2J0:[["$","article",null,{"children":[
3 ["$","h1",null,{"children":"Deep Dive into RSC"}],
4 ["$","$L1",null,{"postId":"123"}],
5 ["$","div",null,{"children":"Here is the rendered article body"}]
6]}]]

Notice how the server component logic and the markdown parser are completely absent from the stream. Only the evaluated strings and the reference to the BookmarkButton component ($L1) are sent down. The client simply walks this tree, instantiates the client components, and seamlessly updates the UI without tearing down the existing DOM state.

Because the wire format represents the React tree rather than raw HTML, navigating between pages preserves client-side state in persistent layout components, audio players, or search inputs without flickering or hard resets.

Eliminating Network Waterfalls

In classic single-page apps, data fetching frequently cascaded into waterfalls. Parent components fetched data, rendered, mounted child components, which then initiated their own fetch calls. With RSC, parallel data fetching happens collocated on the server right next to your database with microsecond latency rather than round-trips over high-latency 4G or 5G mobile networks.

1// Parallel data fetching on the server
2export default async function DashboardPage() {
3 const [metrics, recentOrders, userProfile] = await Promise.all([
4 db.metrics.getOverview(),
5 db.orders.getRecent(),
6 db.users.getCurrentUser(),
7 ]);
8
9 return (
10 <main>
11 <MetricsGrid data={metrics} />
12 <OrdersTable orders={recentOrders} />
13 </main>
14 );
15}

Gotchas to Watch Out For

While RSC is a massive leap forward, there are a few practical rules you must follow:

  • Serializable Props: You cannot pass functions or event handlers from a Server Component to a Client Component as props. Everything crossing that boundary must be JSON-serializable.
  • No Browser APIs in Server Components: Window, document, localStorage, and navigator do not exist in server components. Keep those strictly inside useEffect or event callbacks inside client components.
  • Keep Client Boundaries Low: Resist the temptation to drop 'use client' at the top of your page. Push interactive client components as far down the component tree as possible to maximize server rendering benefits.

Summary

React Server Components redefine the boundary between client and server computing. By offloading data fetching and heavy rendering dependencies to the server while streaming serialized UI updates, your applications gain instant page loads, microscopic client bundle footprints, and a far more resilient architecture.

(Share)

(Next essay)

The Tailwind CSS v4 Revolution: A Rust Compiler Built for Instant HMR →