Componentes del Servidor - Ejecuta componentes React en el servidor con cero JavaScript del lado del cliente
Receta
// 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.
Ejemplo Funcional
// 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:
- Componente del Servidor buscando datos con acceso directo a base de datos/API
Suspensetransmitiendo un componente del servidor lento- Pasando datos serializables (objetos simples, arrays, strings, números) del servidor al cliente
- Componentes del Cliente usando estado y manejadores de eventos
- La directiva de límite
"use client"
Profundización
Cómo Funciona
- Los Componentes del Servidor se ejecutan solo en el servidor durante el renderizado. Su salida es un árbol React serializado (payload de RSC) que se transmite al cliente.
- El payload de RSC no es HTML -- es un formato compacto similar a binario/JSON que React en el cliente puede reconciliar con el DOM existente.
- Los componentes son Componentes del Servidor por defecto. Un archivo debe incluir
"use client"en la parte superior para optar por renderizado del lado del cliente. - Los Componentes del Servidor pueden
importComponentes del Cliente, pero los Componentes del Cliente no pueden importar Componentes del Servidor. En su lugar, pasa Componentes del Servidor comochildrenu otras props JSX. - Las props que cruzan el límite servidor-cliente deben ser serializables: strings, números, booleanos, null, arrays, objetos simples, Dates,
Map,Set,FormData, typed arrays,Promise(conuse()), server actions, y elementos JSX. Las funciones (excepto server actions), clases, y nodos DOM no son serializables. - Los Componentes del Servidor pueden ser
async-- pueden usarawaitdirectamente en el cuerpo de la función. Esto no está permitido en Componentes del Cliente. - Porque los Componentes del Servidor envían cero JavaScript al navegador, las dependencias grandes usadas solo para renderizar (p. ej., resaltadores de sintaxis, analizadores de markdown) no tienen impacto en el bundle del cliente.
Variaciones
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>;
}Notas de TypeScript
- Los Componentes del Servidor asincronos devuelven
Promise<JSX.Element>. TypeScript maneja esto automáticamente con tipos de React 19. - Usa
React.ReactNodepara la propchildrencuando un Componente del Cliente envuelve la salida de un Componente del Servidor. - Los tipos de props serializables deben evitar funciones, instancias de clases, y Symbols.
Gotchas
- Importar un Componente del Cliente en un Componente del Servidor está bien; lo inverso no -- Un Componente del Cliente no puede
importun módulo de Componente del Servidor. Solución: Pasa el Componente del Servidor comochildrenu otra prop JSX en su lugar de importarlo. - Los hooks no están permitidos en Componentes del Servidor --
useState,useEffect,useRef, etc. son solo del cliente. Solución: Mueve la lógica interactiva a un componente"use client". - Los cierres no se serializan -- No puedes pasar una función normal como una prop del servidor al cliente. Solución: Usa una Server Action (
"use server") en su lugar, que React serializa como una referencia RPC. - Las bibliotecas de terceros pueden no ser compatibles con RSC -- Las bibliotecas que usan
useEffecto APIs del navegador fallarán en Componentes del Servidor. Solución: Importalas solo dentro de archivos"use client"o usa un wrapper. - Sin acceso a APIs del navegador --
window,document,localStorageetc. no existen en el servidor. Solución: Cierra el código solo del navegador detrás de"use client". - El re-renderizado funciona diferente -- Los Componentes del Servidor se re-ejecutan en el servidor cuando la ruta cambia. No se re-renderizan en respuesta a cambios de estado. Solución: Eleva el estado interactivo a Componentes del Cliente.
Alternativas
| 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 |
FAQs
¿Qué hace que un componente sea un Componente del Servidor vs un Componente del Cliente?
- Los componentes son Componentes del Servidor por defecto en frameworks que soportan RSC (p. ej., Next.js App Router)
- Un archivo debe incluir
"use client"en la parte superior para optar por renderizado del lado del cliente - Los Componentes del Servidor se ejecutan solo en el servidor y envían cero JavaScript al navegador
¿Puede un Componente del Cliente importar un Componente del Servidor?
- No. Los Componentes del Cliente no pueden importar módulos de Componentes del Servidor
- En su lugar, pasa Componentes del Servidor como
childrenu otras props JSX a Componentes del Cliente - Un Componente del Servidor puede importar Componentes del Cliente sin restricciones
¿Qué tipos de datos se pueden pasar como props desde un Componente del Servidor a un Componente del Cliente?
- Strings, números, booleanos, null, arrays, objetos simples, Dates,
Map,Set,FormData, typed arrays Promise(consumido conuse()) y server actions también son serializables- Las funciones (excepto server actions), instancias de clases, y nodos DOM no son serializables
¿Puedo usar useState, useEffect, u otros hooks dentro de un Componente del Servidor?
- No. Los hooks como
useState,useEffect,useRef, etc. son solo del cliente - Los Componentes del Servidor pueden usar
awaitdirectamente en el cuerpo de la función en su lugar - Mueve la lógica interactiva a un componente
"use client"
¿Cómo buscas datos en paralelo en un Componente del Servidor?
async function Dashboard() {
const [users, posts, stats] = await Promise.all([
fetchUsers(),
fetchPosts(),
fetchStats(),
]);
return <>{/* renderizar datos */}</>;
}¿Qué es el payload de RSC y cómo funciona el streaming?
- El payload de RSC es un formato compacto similar a binario/JSON (no HTML) que React en el cliente reconcilia con el DOM existente
- Los Componentes del Servidor se pueden envolver en límites
<Suspense>para streaming -- el fallback se muestra mientras el componente se resuelve - Esto permite que la página sea parcialmente interactiva antes de que todos los datos se hayan cargado
Gotcha: Una biblioteca de terceros se rompe cuando se usa en un Componente del Servidor. ¿Por qué?
- Las bibliotecas que usan
useEffect,useState, o APIs del navegador (window,document) fallarán en Componentes del Servidor - Importalas solo dentro de archivos
"use client"o crea un componente wrapper de cliente delgado
Gotcha: ¿Por qué mi Componente del Servidor no se re-renderiza cuando el estado del cliente cambia?
- Los Componentes del Servidor se re-ejecutan en el servidor solo cuando la ruta cambia
- Ellos no se re-renderizan en respuesta a cambios de estado del lado del cliente
- Eleva el estado interactivo a Componentes del Cliente si necesitas reactividad
¿Cómo pasas una promesa de un Componente del Servidor a un Componente del Cliente para carga diferida?
// 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.
¿Cómo escribes un Componente del Servidor asincronos en TypeScript?
- Los Componentes del Servidor asincronos devuelven
Promise<JSX.Element>-- TypeScript maneja esto automáticamente con tipos de React 19 - Usa
React.ReactNodepara la propchildrencuando un Componente del Cliente envuelve la salida de un Componente del Servidor - Los tipos de props serializables deben evitar funciones, instancias de clases, y Symbols
¿Cuál es el patrón de composición para envolver Componentes del Servidor en Componentes del Cliente?
"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.
¿Las dependencias grandes usadas solo en Componentes del Servidor afectan el bundle del cliente?
- No. Porque los Componentes del Servidor envían cero JavaScript al navegador, las dependencias grandes como resaltadores de sintaxis o analizadores de markdown no tienen impacto en el bundle del cliente
- Este es un beneficio clave de rendimiento de la arquitectura RSC
Relacionado
- Descripción general -- Qué hay de nuevo en React 19
- Server Actions -- Mutando datos desde funciones del servidor
- use() Hook -- Consumiendo promesas pasadas desde Componentes del Servidor
- Asset Loading -- Precargando recursos junto con contenido renderizado del servidor