React Server Components: What Actually Changed
React Server Components represent the biggest shift in React's mental model since hooks. After a year of building with them in production, here's what I wish someone had told me at the start.
The core concept is simple: some components run only on the server. They can access databases, read files, and call APIs directly, no useEffect, no loading states, no API routes. The HTML they produce is streamed to the client. They never ship JavaScript to the browser.
The mental model shift is significant. In the old world, every component was a client component. Data fetching happened in useEffect or getServerSideProps. Now, the default is server. You opt into client behavior with 'use client' when you need interactivity, event handlers, state, effects, browser APIs.
The boundary between server and client components is where most confusion lives. A server component can render a client component, but a client component cannot import a server component. You can pass server component output as children to a client component. This constraint shapes your component architecture.
Data fetching became dramatically simpler. A server component that needs data just awaits a database query at the top of the function. No useEffect. No loading state management. No stale data bugs. The component renders with the data it needs. For most pages, this eliminates an entire category of complexity.
The performance benefits are real but nuanced. Server components reduce JavaScript bundle size, components that don't need interactivity send zero JS. But the initial page load still requires a server round-trip. For highly dynamic pages, the streaming model (with Suspense boundaries) provides a progressive loading experience that feels fast.
Where I've seen teams struggle: over-extracting client components. The temptation is to add 'use client' the moment you need an onClick handler. Instead, push the client boundary as deep as possible. A page-level server component can render most of the UI, with a small interactive widget as a client component leaf.
Forms are the killer use case. Server Actions let you handle form submissions without API routes. The form component is a server component. The action function runs on the server. Validation, database writes, and redirects happen in one place. This pattern eliminates an enormous amount of boilerplate.
My advice: stop thinking in terms of 'server components vs client components' and start thinking about 'where does this code need to run?' If it needs the DOM or user interaction, it's client. Everything else is server by default.