Busca en todas las páginas de la documentación
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
// app/page.tsx (Componente del Servidor -- el predeterminado en Next.js App Router)
import { db } from "@/lib/db";
import ClientCounter from "./ClientCounter";
export default async function DashboardPage() {
// Acceso directo a la base de datos -- este código nunca llega al navegador
const stats = await db.query("SELECT count(*) FROM orders");
return (
<main>
<h1>Dashboard</h1>
<p>Total orders: {stats.count}</p>
{/* Entrega a un Componente del Cliente para interactividad */}
<ClientCounter initialCount={stats.count} />
</main>
);
}// app/ClientCounter.tsx
"use client";
import { useState } from "react";
export default function ClientCounter({ initialCount }: { initialCount: number }) {
const [count, setCount] = useState(initialCount);
return <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>;
}Cuándo usarlo: Usa Componentes del Servidor cuando un componente solo necesita leer datos y renderizar HTML -- sin manejadores de eventos, sin estado, sin APIs del navegador.
// Un página de catálogo de productos que mezcla preocupaciones del servidor y cliente
// app/products/page.tsx (Componente del Servidor)
import { Suspense } from "react";
import { getProducts, getCategories } from "@/lib/api";
import ProductGrid from "./ProductGrid";
import CategoryFilter from "./CategoryFilter";
export default async function ProductsPage() {
const categories = await getCategories();
return (
<div className="flex gap-6">
{/* Componente del Cliente para filtrado interactivo */}
<CategoryFilter categories={categories} />
{/* Componente del Servidor con streaming */}
<Suspense fallback={<p>Loading products...</p>}>
<ProductList />
</Suspense>
</div>
);
}
async function ProductList() {
const products = await getProducts();
// ProductGrid es un componente "use client" que recibe props serializables
return <ProductGrid products={products} />;
}// app/products/CategoryFilter.tsx
"use client";
import { useState, useTransition } from "react";
import { useRouter } from "next/navigation";
type Props = { categories: { id: string; name: string }[] };
export default function CategoryFilter({ categories }: Props) {
const [selected, setSelected] = useState<string | null>(null);
const [isPending, startTransition] = useTransition();
const router = useRouter();
function handleSelect(id: string) {
setSelected(id);
startTransition(() => {
router.push(`/products?category=${id}`);
});
}
return (
<aside>
<h2>Categories</h2>
<ul>
{categories.map((c) => (
<li key={c.id}>
<button
onClick={() => handleSelect(c.id)}
className={selected === c.id ? "font-bold" : ""}
>
{c.name}
</button>
</li>
))}
</ul>
{isPending && <p>Filtering...</p>}
</aside>
);
}// app/products/ProductGrid.tsx
"use client";
type Product = { id: string; name: string; price: number };
export default function ProductGrid({ products }: { products: Product[] }) {
return (
<div className="grid grid-cols-3 gap-4">
{products.map((p) => (
<div key={p.id} className="border p-4 rounded">
<h3>{p.name}</h3>
<p>${p.price.toFixed(2)}</p>
<button onClick={() => alert(`Added ${p.name}`)}>Add to cart</button>
</div>
))}
</div>
);
}Lo que esto demuestra:
Suspense transmitiendo un componente del servidor lento"use client""use client" en la parte superior para optar por renderizado del lado del cliente.import Componentes del Cliente, pero los Componentes del Cliente no pueden importar Componentes del Servidor. En su lugar, pasa Componentes del Servidor como children u otras props JSX.Map, Set, FormData, typed arrays, Promise (con use()), server actions, y elementos JSX. Las funciones (excepto server actions), clases, y nodos DOM no son serializables.async -- pueden usar await directamente en el cuerpo de la función. Esto no está permitido en Componentes del Cliente.Patrones de obtención de datos asincronos:
// Patrón 1: Await de nivel superior
async function UserProfile({ userId }: { userId: string }) {
const user = await fetchUser(userId);
return <h1>{user.name}</h1>;
}
// Patrón 2: Obtención de datos en paralelo
async function Dashboard() {
const [users, posts, stats] = await Promise.all([
fetchUsers(),
fetchPosts(),
fetchStats(),
]);
return (
<>
<UserList users={users} />
<PostFeed posts={posts} />
<StatsPanel stats={stats} />
</>
);
}
// Patrón 3: Pasa promesa al cliente (aplaza resolución)
async function Page() {
const dataPromise = fetchSlowData(); // NO hagas await
return <ClientChart dataPromise={dataPromise} />;
}Patrón de composición -- pasando Componentes del Servidor como children:
// Esto funciona porque children ya es JSX renderizado, no una importación
"use client";
export function ClientLayout({ children }: { children: React.ReactNode }) {
const [open, setOpen] = useState(true);
return <div className={open ? "expanded" : "collapsed"}>{children}</div>;
}Promise<JSX.Element>. TypeScript maneja esto automáticamente con tipos de React 19.React.ReactNode para la prop children cuando un Componente del Cliente envuelve la salida de un Componente del Servidor.import un módulo de Componente del Servidor. Solución: Pasa el Componente del Servidor como children u otra prop JSX en su lugar de importarlo.useState, useEffect, useRef, etc. son solo del cliente. Solución: Mueve la lógica interactiva a un componente "use client"."use server") en su lugar, que React serializa como una referencia RPC.useEffect o APIs del navegador fallarán en Componentes del Servidor. Solución: Importalas solo dentro de archivos "use client" o usa un wrapper.window, document, localStorage etc. no existen en el servidor. Solución: Cierra el código solo del navegador detrás de "use client".| Enfoque | Cuándo elegir |
|---|---|
| Componentes del Servidor | UI de solo lectura, obtención de datos, dependencias grandes que deseas fuera del bundle |
| Componentes del Cliente | UI interactiva con estado, efectos, o APIs del navegador |
| Renderizado del lado del servidor (SSR) sin RSC | Aplicaciones anteriores a React 19, frameworks que aún no soportan RSC |
| Generación de sitio estático (SSG) | Contenido que cambia raramente y puede construirse en el tiempo de despliegue |
| Rutas API + fetch del cliente | Cuando necesitas control detallado sobre caché y forma de datos |
"use client" en la parte superior para optar por renderizado del lado del clientechildren u otras props JSX a Componentes del ClienteMap, Set, FormData, typed arraysPromise (consumido con use()) y server actions también son serializablesuseState, useEffect, useRef, etc. son solo del clienteawait directamente en el cuerpo de la función en su lugar"use client"async function Dashboard() {
const [users, posts, stats] = await Promise.all([
fetchUsers(),
fetchPosts(),
fetchStats(),
]);
return <>{/* renderizar datos */}</>;
}<Suspense> para streaming -- el fallback se muestra mientras el componente se resuelveuseEffect, useState, o APIs del navegador (window, document) fallarán en Componentes del Servidor"use client" o crea un componente wrapper de cliente delgado// Componente del Servidor -- NO hagas await
async function Page() {
const dataPromise = fetchSlowData();
return <ClientChart dataPromise={dataPromise} />;
}El componente del cliente usa use(dataPromise) dentro de un límite <Suspense> para resolverla.
Promise<JSX.Element> -- TypeScript maneja esto automáticamente con tipos de React 19React.ReactNode para la prop children cuando un Componente del Cliente envuelve la salida de un Componente del Servidor"use client";
export function ClientLayout({ children }: { children: React.ReactNode }) {
const [open, setOpen] = useState(true);
return <div className={open ? "expanded" : "collapsed"}>{children}</div>;
}El children ya es JSX renderizado de un Componente del Servidor, no una importación.
Revisado por Chris St. John·Última actualización: 16 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥