React Server Components: Boundaries, Async, and Tradeoffs
React Server Components (RSC) let a React application render some components on the server without sending their component code to the browser. The key design choice is the boundary between server and client components: it determines where data access and rendering happen, which code is shipped, and how interactivity and asynchronous work fit together.
What React Server Components change
In a traditional client-rendered React application, the browser downloads JavaScript for components, runs them, and often fetches data after the page loads. Server-side rendering can send pre-rendered HTML, but the browser commonly still downloads the component code and hydrates the page to make it interactive. RSC introduce a distinct category: a server component runs on the server and its implementation is not included in the client JavaScript bundle.
RSC are a React capability, not a replacement for React or a synonym for server-side rendering. They can be used alongside server-rendered HTML and client components. A framework or other supporting integration handles the server, transport, and build details; a plain browser-only React setup cannot simply execute server components on its own.
The server sends a serialized description of the rendered component tree, often called the RSC payload. A framework can use this alongside HTML for the initial page and later updates. The payload is not a screenshot: React uses it to reconcile the interface, while client components provide browser-side behavior.
Server and client boundaries
In frameworks that use the convention, a file beginning with the directive 'use client' marks a client-module boundary. The directive does not mean that only one component in the file can run on the server; rather, that module and its imported dependency graph are treated as client code. Components imported beneath that boundary generally become part of the client side of the tree.
Modules without that directive can be server components when rendered within an RSC-capable environment. A server component can import another server component, read data using server-side facilities provided by its environment, and render client components. A client component can render server-rendered children passed to it as props, but it cannot import a server component and execute it in the browser.
For example, a product page might fetch product details in a server component and pass a small interactive quantity selector to a client component:
// Server component
async function ProductPage({ id }) {
const product = await getProduct(id);
return <ProductDetails product={product} />;
}
// Client component
'use client';
function QuantitySelector() {
const [quantity, setQuantity] = useState(1);
return <button onClick={() => setQuantity(quantity + 1)}>
Quantity: {quantity}
</button>;
}
The example is schematic: the supported data-fetching method and file conventions depend on the framework. The important point is to keep browser-only state and event handlers on the client side, while leaving static or data-oriented work on the server when that makes sense.
How asynchronous rendering works
Server components can be asynchronous in environments that support them. A component can await a promise for data before returning its rendered output. This allows data fetching to be colocated with the component that needs the result, rather than requiring every request to be gathered in a single top-level client effect.
When a part of the tree is waiting, React’s Suspense mechanism can show a fallback for that region while work continues. A framework may stream the initial response or later component updates, allowing ready portions to appear before slower portions finish. The exact streaming behavior, caching rules, and navigation lifecycle are framework-dependent; Suspense is the React coordination mechanism, not a guarantee that every integration streams every request.
Async work can be structured in parallel or in sequence. If two independent components each start their own request, an implementation may be able to make progress concurrently. But if one component waits for a value before it can start another request, the work is sequential. Avoid assuming that moving every fetch into a component automatically removes waterfalls: inspect the dependency chain and use the framework’s data-loading and caching guidance.
What belongs on each side
Choose a server component when its job is to produce output from data or server-side resources and it does not need browser interaction. Choose a client component for state, event handlers, browser APIs, effects, or libraries that require a browser. A page can combine many server components with a few carefully placed client islands.
- Good server-side candidates: article text, product information, navigation assembled from server data, and components that access a database through an appropriate server-side layer.
- Good client-side candidates: a search field with live interaction, a menu that responds to clicks, a chart with browser-side controls, or a component using local state.
- Boundary checks: props crossing from server to client must be serializable under the relevant React integration. Functions, open database connections, and other live server objects cannot simply be passed as ordinary props.
Keep secrets and privileged operations in server-only code, but do not treat a server component as a security policy by itself. Validate authorization at the data-access or action boundary, and avoid returning private data in rendered output or props. Anything sent to the client can be inspected there.
Benefits and tradeoffs
RSC can reduce the JavaScript that needs to be delivered for parts of an application that do not require client behavior. They can also keep server-only dependencies out of the browser bundle and make it natural to fetch data close to the UI that uses it. These gains can help performance, especially when a page contains substantial non-interactive content.
There are costs. The application now has distinct server and client execution environments, so developers must reason about module boundaries, serialization, request lifetimes, and how navigation updates are transported. Debugging may involve server logs and client tools. A component that accidentally pulls a large dependency into a client boundary can erase bundle savings. Server rendering also consumes server resources and does not automatically make a slow database query fast.
RSC can improve the delivery model, but they do not guarantee faster pages, fewer network requests, or better user experience. Measure the actual application: initial response time, client bundle size, interaction responsiveness, server load, and behavior on slow connections. A mostly interactive tool may benefit less than a content-heavy page with limited interaction.
Common misconceptions and problems
- “Server components replace client components.” They complement them. Browser interaction still needs client-side code.
- “Server components are just server-side rendering.” SSR can produce HTML for client components; RSC also define a server-only component model and a payload for reconciling the tree.
- “Every component can use hooks and event handlers.” Browser state hooks and event handlers belong in client components. Keep the client boundary where those features are needed.
- “An async component makes the whole page wait.” Suspense boundaries can provide fallbacks and let other work proceed, but the user-visible result depends on boundary placement and framework behavior.
When a build reports that a server-only module is being used in a client component, trace imports from the nearest client boundary. Move the server-dependent work into a server component and pass the needed serializable result down, or find a browser-compatible dependency. When a page shows a loading fallback longer than expected, check whether a request is slow, serialized behind another request, or placed outside the boundary intended to handle it.
Frequently Asked Questions
Do React Server Components run in the browser?
Server components execute in the server environment and their implementation is not sent as client JavaScript. The browser can render and update the resulting interface, including client components in the tree.
Can a server component use useState?
No. A server component does not run as an interactive browser component. Put state and event handling in a client component, marked with 'use client' where the framework requires it.
Can a client component import a server component?
It cannot import and execute a server component as browser code. A server component can instead render a client component and pass rendered server content as a prop, such as a children slot.
Do Server Components eliminate hydration?
No. Server components themselves are not hydrated as client components, but client components may still need hydration to attach browser behavior. The amount depends on which parts of the page are interactive.
Should every page use Server Components?
Not necessarily. They are most useful when server-side data access and reduced client code fit the page. For highly interactive or simple client-only interfaces, the added server/client boundary may provide little benefit.
Related
Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.