React Server Components were introduced in React 19 and brought a variety of new capabilities. Put simply, these components can read the data they need directly on the backend, or communicate with any other service on the backend — like reading data straight from a database.
For example, take a look at the code below to see how much simpler the code becomes for a Server Component.
With this simple logic for reading and displaying data, our code's readability is a lot better, and this doesn't just lead to simpler coding. In a server component, the process of reading data and rendering HTML happens on the server side, and the end user immediately sees the result quickly, without needing to read data and render on the client side — which helps improve our page load and our website's SEO. Now, in frameworks with SSR support like Next.js, we already had server rendering, but there's a fundamental difference here that I'll touch on very briefly.
Waterfall: We had to wait for all of a page's data to load before showing the user anything. But in RSF, using the streaming technique, each piece of the page loads separately.
Prop Drilling: We had to fetch data at the root of the page and pass it down layer by layer. (In Next.js, getServerSideProps)
Tight Coupling: The child component was dependent on the parent.
But in RSC, each component is responsible for its own data. If the Sidebar needs the list of menus, it reads that directly from the database or API itself, without involving the main page.
export default async function Sidebar() {
const cats = await db.category.findMany();
// rest of the code...
}
Other advantages of RSC:
Security: Since RSC code runs on the server side, you no longer need to worry about tokens leaking, and you can even talk to the database directly and write your whole project on this stack. For example:
import { Client } from "pg";
export default async function UsersPage() {
const client = new Client({ connectionString: process.env.DATABASE_URL });
await client.connect();
const res = await client.query("SELECT id, name, email FROM users");
await client.end();
return res.rows.map((u) => (
{u.name} ({u.email})
));
}
Smaller bundle size: Because all the rendering code runs on the server side, no client-side JavaScript code is generated for it, which helps reduce network load, and since we no longer have a hydration step, our site puts less load on the user's system. (Note that we have no JavaScript events, such as onClick, onChange, etc., on elements inside an RSC.)
Drawbacks of RSC:
New mental model: For teams that have worked with traditional React for years, this shift in mindset and change in best practices (data fetching, state, component tree structure) makes things harder and slower.
Limited interactivity inside RSC: a Server Component itself can't have event handlers (onClick, onChange, …), use useState, useEffect, etc., or perform real-time UI updates. You need to put a Client Component in the middle for any interaction.
Less compatibility with third-party libraries: many React libraries use useEffect, window, document, events, and so on — meaning they're inherently client-side and don't work on RSC.
Harder debugging: when DevTools, logging, stack traces, etc. are split between server and client, it becomes harder to track exactly where in the process a problem happened (server or client?).