React Server Components: Guia Practica y Patrones
Guia practica de React Server Components. Cuando usarlos, patrones de composicion, data fetching, y como combinar Server y Client Components en Next.js.
React Server Components: Guia Practica y Patrones
React Server Components (RSC) cambiaron como se construyen apps en React. En vez de enviar todo el JavaScript al navegador y renderizar ahi, los Server Components se ejecutan en el servidor y envian solo el HTML resultante. Menos JavaScript = carga mas rapida.
Si ya leiste la comparativa Server vs Client Components, aqui vamos mas profundo: patrones de composicion, data fetching, y como estructurar tu app para aprovecharlos al maximo.
La regla de oro
En Next.js (App Router), todo es Server Component por default. Solo agregas "use client" cuando necesitas interactividad: estado, eventos, hooks del navegador.
// Server Component (default, sin directiva)
export default async function UserProfile({ userId }: { userId: string }) {
const user = await db.user.findUnique({ where: { id: userId } });
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
{/* Este componente es Client porque tiene onClick */}
<FollowButton userId={userId} />
</div>
);
}// Client Component (necesita "use client" porque usa estado)
"use client";
import { useState } from "react";
export function FollowButton({ userId }: { userId: string }) {
const [following, setFollowing] = useState(false);
return (
<button onClick={() => setFollowing(!following)}>
{following ? "Siguiendo" : "Seguir"}
</button>
);
}Patron 1: Server-first data fetching
El patron mas importante. Los datos se traen en el servidor, la interactividad vive en el cliente:
// app/productos/page.tsx (Server Component)
export default async function ProductosPage() {
// Esto corre en el servidor, nunca expone tu DB al cliente
const productos = await db.product.findMany({
where: { active: true },
orderBy: { createdAt: "desc" },
});
return (
<div>
<h1>Productos</h1>
{/* El Client Component recibe datos ya resueltos */}
<ProductGrid productos={productos} />
</div>
);
}// components/ProductGrid.tsx (Client Component)
"use client";
import { useState } from "react";
type Producto = { id: string; name: string; price: number; category: string };
export function ProductGrid({ productos }: { productos: Producto[] }) {
const [filtro, setFiltro] = useState("todos");
const filtrados = filtro === "todos"
? productos
: productos.filter((p) => p.category === filtro);
return (
<>
<select onChange={(e) => setFiltro(e.target.value)}>
<option value="todos">Todos</option>
<option value="ropa">Ropa</option>
<option value="tech">Tech</option>
</select>
<div className="grid grid-cols-3 gap-4">
{filtrados.map((p) => (
<div key={p.id}>
<p>{p.name}</p>
<p>${p.price}</p>
</div>
))}
</div>
</>
);
}El Server Component hace la query. El Client Component maneja el filtro interactivo. El JavaScript de Prisma y la logica de la query nunca llegan al navegador.
Patron 2: Composicion (Server dentro de Client)
Un Client Component puede recibir Server Components como children:
// layout con interactividad que envuelve contenido del servidor
"use client";
import { useState } from "react";
export function Sidebar({ children }: { children: React.ReactNode }) {
const [open, setOpen] = useState(true);
return (
<div className="flex">
<aside className={open ? "w-64" : "w-0"}>
<button onClick={() => setOpen(!open)}>Toggle</button>
<nav>{/* menu */}</nav>
</aside>
<main>{children}</main> {/* children puede ser Server Component */}
</div>
);
}// page.tsx (Server Component)
import { Sidebar } from "./Sidebar";
export default async function Page() {
const data = await fetchData(); // corre en servidor
return (
<Sidebar>
{/* Este contenido se renderiza en el servidor */}
<div>{data.map(/* ... */)}</div>
</Sidebar>
);
}El Sidebar es Client (maneja estado del toggle), pero su children se renderiza en el servidor. Lo mejor de ambos mundos.
Patron 3: Async components con Suspense
Los Server Components pueden ser async directamente, lo que permite streaming:
import { Suspense } from "react";
export default function DashboardPage() {
return (
<>
<h1>Dashboard</h1>
<Suspense fallback={<p>Cargando stats...</p>}>
<Stats />
</Suspense>
<Suspense fallback={<p>Cargando actividad...</p>}>
<RecentActivity />
</Suspense>
</>
);
}
// Cada componente carga independientemente
async function Stats() {
const stats = await getStats(); // puede tardar 200ms
return <div>{/* renderizar stats */}</div>;
}
async function RecentActivity() {
const activity = await getActivity(); // puede tardar 2s
return <div>{/* renderizar actividad */}</div>;
}El usuario ve las stats en 200ms sin esperar los 2 segundos de la actividad. Revisa la guia de Streaming y Suspense para mas patrones.
Que NO puede hacer un Server Component
| Necesitas esto? | Usa Client Component |
|---|---|
useState, useReducer | Si |
useEffect, useLayoutEffect | Si |
Event handlers (onClick, onChange) | Si |
Browser APIs (window, localStorage) | Si |
Context providers (useContext) | Si |
| Hooks custom que usan hooks de React | Si |
Todo lo demas -- fetch de datos, acceso a DB, lectura de archivos, renderizado de contenido estatico -- va en Server Components.
Errores comunes
Poner "use client" en todo: si tu componente no usa hooks ni eventos, no necesita ser Client Component. Dejalo como Server Component y ahorra JavaScript.
Pasar funciones como props: las props que van de Server a Client deben ser serializables. No puedes pasar funciones, clases ni objetos complejos. Pasa datos primitivos o POJOs.
// MAL: no puedes pasar una funcion de servidor al cliente
<ClientComponent onSave={async () => await db.save()} />
// BIEN: usa Server Actions
<ClientComponent />
// Dentro del Client Component, llama a una Server Action importadaSiguiente paso
Si quieres entender las diferencias basicas primero, lee la comparativa Server vs Client Components. Y para combinar RSC con data fetching desde PostgreSQL, la guia de PostgreSQL para devs TypeScript cierra el circuito.
Preguntas frecuentes
Cual es la diferencia entre Server Components y Client Components?
Server Components se renderizan en el servidor y envian HTML al cliente. No incluyen JavaScript en el bundle del navegador. Client Components se renderizan en el cliente y pueden usar hooks, eventos y estado. En Next.js, todos los componentes son Server Components por default.
Puedo usar useState en un Server Component?
No. Los Server Components no tienen estado ni lifecycle en el cliente. Si necesitas useState, useEffect o event handlers, ese componente debe ser un Client Component con la directiva 'use client' al inicio del archivo.
Como paso datos de un Server Component a un Client Component?
Via props. El Server Component hace el fetch de datos y los pasa como props al Client Component. Los datos deben ser serializables (no puedes pasar funciones ni clases). Este patron se llama 'server-first data fetching'.
Los Server Components mejoran el rendimiento?
Si, significativamente. Reducen el JavaScript que envias al navegador porque el codigo del componente nunca llega al cliente. Solo el HTML resultante. Esto mejora el Time to Interactive y reduce el bundle size.
Articulos relacionados
Next.js 16: Guia de Migracion y Novedades
Migra tu proyecto de Next.js 15 a 16. Novedades principales, breaking changes, y pasos para actualizar sin romper tu app.
Testing en Next.js con Vitest y Playwright
Configura testing en tu proyecto Next.js. Unit tests con Vitest, E2E con Playwright, y como integrarlos en tu pipeline de CI/CD.
Tailwind CSS 4: Migracion desde v3
Migra tu proyecto de Tailwind CSS 3 a 4. Cambios principales, nuevo sistema de configuracion, CSS-first config y como actualizar sin romper tu app.