Designing Components for Server Boundaries
Learn how to reason about component design when rendering spans server and client environments, and how those boundaries determine data flow, performance, and UI stability.
In traditional React applications, component design is mostly a local concern. We decide how to split components based on readability, reuse, or separation of concerns, assuming everything ultimately runs in the browser. When performance problems appear, slow pages, heavy bundles, awkward loading states. We usually try to fix them inside that model: memoization, code splitting, conditional rendering, or more granular loading spinners.
As applications grow, this approach starts to break down. A single component may need data, layout, and interactivity, but those responsibilities pull in opposite directions. Fetching data early improves perceived performance, but it also couples the component to network latency. Adding interactivity forces the entire component into the client bundle, even if most of its output is static. To compensate, we often add flags, skeletons, and defensive logic, but the UI still feels unstable, content flashes, layouts shift, and transitions feel arbitrary.
React 19 changes the terrain entirely by allowing components to render on the server by default. This is not just a performance feature; it introduces multiple rendering environments with different costs and capabilities. The problem is that many existing component designs assume a single environment. When those designs are carried forward unchanged, we accidentally ship too much code to the client, block streaming, or reintroduce waterfalls we thought we had eliminated. Server boundaries exist to solve these problems, but only if we redesign components with those boundaries in mind.