React Server Components
React Server Components (RSC) represent a paradigm shift in React's execution model. Unlike traditional React components that always run in the browser, Server Components execute exclusively on the server. They can access data sources directly, reduce client-side JavaScript, and enable streaming responses. This article explores the architecture, patterns, and practical implications of RSC.
How Server Components Work
Server Components run once on the server during rendering. They can be async functions that directly await data from databases, file systems, or APIs. The server executes the component, produces a serializable description of the output, and sends it to the client. The client never receives the Server Component's code.
This is fundamentally different from Server-Side Rendering (SSR). SSR renders components to HTML on the server but still sends all the JavaScript to the client for hydration. Server Components send no JavaScript to the client—only the rendered output. The client-side JavaScript is significantly smaller.
Streaming
Server Components support streaming. The server can send parts of the page as they become available, rather than waiting for the entire page to render. The client progressively displays content, showing faster-loading sections while waiting for slower data sources.
Streaming is achieved through React's Suspense boundaries. A component wrapped in `}>` allows the server to stream the fallback immediately and replace it with the component's output when ready. This creates a progressive rendering experience without complex client-side loading states.
Data Fetching
Server Components can fetch data directly without building API endpoints. A component can connect to a database, read from the file system, or call external APIs. The data fetching code runs only on the server, eliminating the need to expose data access services to the client.
Data fetching in Server Components is natural and direct. You write `const data = await db.query(...)` inside the component. No `useEffect`, no state management for loading states, no custom hooks. The component fetches data and renders it in the same function.
Client Boundaries
The boundary between Server and Client Components is marked by `'use client'`. Everything is a Server Component by default. When a component needs interactivity—event handlers, state, effects, browser APIs—you add `'use client'` to make it a Client Component.
Client Components are still rendered on the server for the initial HTML (like SSR) but are hydrated on the client. They can import other Client Components but cannot import Server Components. Server Components can import and render Client Components, passing serializable props.
The placement of client boundaries is a key architectural decision. Pushing boundaries deep into the component tree maximizes the benefits of Server Components. Only the interactive leaf components need to be Client Components, while their parents and siblings remain as Server Components.
Bundle Size Impact
The most significant practical benefit of Server Components is reduced bundle size. Libraries used for date formatting, markdown parsing, syntax highlighting, or data fetching run only on the server. Their code is never included in the client bundle.
For example, a blog post rendered with a markdown library on the server sends only the HTML to the client. The markdown library, which could be hundreds of kilobytes, is never downloaded by the browser. The cumulative impact across an application can reduce bundle sizes by 30-50%.
Architectural Considerations
Server Components change how you think about component architecture. Components are no longer always interactive—they are primarily data transformers that produce UI. The mental model shifts from "components as interactive widgets" to "components as UI functions" that can optionally add interactivity.
This model encourages a more data-oriented architecture. Components fetch and render data in the same place. The data flow is clear and predictable. There is less ceremony around data fetching, state management, and prop drilling.
Server Components are not appropriate for all scenarios. Components with real-time updates, extensive client-side state, or complex interactivity are better as Client Components. The art of RSC architecture is choosing the right boundary for each component based on its interactivity needs.