Componentes del Servidor
Renderiza componentes React en el servidor con cero JavaScript del lado del cliente -- el comportamiento predeterminado en el Next.js App Router.
Busca en todas las páginas de la documentación
Renderiza componentes React en el servidor con cero JavaScript del lado del cliente -- el comportamiento predeterminado en el Next.js App Router.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Tarjeta de referencia rápida -- lista para copiar y pegar.
// app/page.tsx -- Server Component de forma predeterminada (no se necesita directiva)
import { db } from "@/lib/db";
export default async function HomePage() {
const posts = await db.post.findMany({ take: 10 });
return (
<main>
<h1>Últimas Publicaciones</h1>
<ul>
{posts.map((p) => (
<li key={p.id}>{p.title}</li>
))}
</ul>
</main>
);
}Cuándo usarlo: Cualquier componente que solo lea datos y renderice markup -- sin useState, sin useEffect, sin event handlers, sin browser APIs. Este es el comportamiento predeterminado; tu opt out es con "use client", no tu opt in.
// app/blog/page.tsx (Server Component)
import { Suspense } from "react";
import { formatDistanceToNow } from "date-fns";
type Post = {
id: string;
title: string;
excerpt: string;
publishedAt: string;
author: { name: string; avatar: string };
};
async function fetchPosts(): Promise<Post[]> {
const res = await fetch("https://api.example.com/posts", {
next: { revalidate: 300 },
});
if (!res.ok) throw new Error("Error al obtener publicaciones");
return res.json();
}
export default async function BlogPage() {
return (
<main className="max-w-3xl mx-auto p-6">
<h1 className="text-3xl font-bold mb-8">Blog</h1>
<Suspense fallback={<PostsSkeleton />}>
<PostList />
</Suspense>
</main>
);
}
async function PostList() {
const posts = await fetchPosts();
return (
<div className="space-y-8">
{posts.map((post) => (
<article key={post.id} className="border-b pb-6">
<h2 className="text-xl font-semibold mb-2">{post.title}</h2>
<p className="text-gray-600 mb-3">{post.excerpt}</p>
<div className="flex items-center gap-3 text-sm text-gray-500">
<img
src={post.author.avatar}
alt={post.author.name}
className="w-6 h-6 rounded-full"
/>
<span>{post.author.name}</span>
<span>
{formatDistanceToNow(new Date(post.publishedAt), {
addSuffix: true,
})}
</span>
</div>
</article>
))}
</div>
);
}
function PostsSkeleton() {
return (
<div className="space-y-8">
{Array.from({ length: 3 }).map((_, i) => (
<div key={i} className="border-b pb-6">
<div className="h-6 bg-gray-200 rounded w-3/4 mb-2 animate-pulse" />
<div className="h-4 bg-gray-100 rounded w-full mb-3 animate-pulse" />
<div className="h-4 bg-gray-100 rounded w-1/2 animate-pulse" />
</div>
))}
</div>
);
}Lo que esto demuestra:
await directamente en el cuerpo de la funcióndate-fns) que envía cero JavaScript al cliente<Suspense> para streaming"use client" en ningún lado -- toda la página es renderizada en el servidorasync y usar await directamente. Esto no está permitido en Client Components.date-fns) no se incluyen en el bundle del cliente.revalidatePath/revalidateTag.Obtención de datos en paralelo:
async function Dashboard() {
const [users, revenue, orders] = await Promise.all([
fetchUsers(),
fetchRevenue(),
fetchOrders(),
]);
return (
<>
<UserTable users={users} />
<RevenueChart revenue={revenue} />
<OrderList orders={orders} />
</>
);
}Pasando datos del servidor a Client Components:
// Server Component
import { ClientMap } from "./client-map";
export default async function LocationPage() {
const locations = await db.location.findMany();
// Solo los datos serializables pueden cruzar el límite
return <ClientMap locations={locations} />;
}Utilidades solo del servidor:
// lib/server-only-utils.ts
import "server-only"; // Lanza un error de compilación si se importa en un Client Component
export function getSecretConfig() {
return {
apiKey: process.env.SECRET_API_KEY!,
dbUrl: process.env.DATABASE_URL!,
};
}// Los Server Components async retornan Promise<JSX.Element>
// TypeScript maneja esto con los tipos de React 19+
async function MyComponent(): Promise<JSX.Element> {
const data = await fetchData();
return <div>{data.name}</div>;
}
// Los props deben ser serializables cuando se pasan a Client Components
type SerializableProps = {
name: string;
count: number;
items: { id: string; label: string }[];
// NO permitido: onClick: () => void
// NO permitido: ref: React.Ref<HTMLDivElement>
};
// Usa el paquete `server-only` para protección en tiempo de compilación
import "server-only";No puedes usar hooks -- useState, useEffect, useRef y todos los otros hooks son solo del cliente. Solución: Extrae partes interactivas en un componente "use client".
No puedes usar event handlers -- onClick, onChange, onSubmit, etc. requieren JavaScript del lado del cliente. Solución: Mueve la lógica del event handler a un Client Component.
No puedes acceder a browser APIs -- window, document, localStorage, navigator no están disponibles en el servidor. Solución: Usa estos solo en componentes "use client" o detrás de comprobaciones typeof window !== "undefined".
Los props a Client Components deben ser serializables -- Las funciones (excepto Server Actions), instancias de clases, Symbols y nodos DOM no pueden pasarse como props cruzando el límite servidor-cliente. Solución: Pasa solo datos simples; usa Server Actions para callbacks.
Payloads del servidor grandes -- Obtener demasiados datos en un Server Component y pasar todo como props infla el payload RSC. Solución: Obtén solo lo que el Client Component necesita; pagina en el servidor.
Las bibliotecas de terceros pueden no ser compatibles con RSC -- Las bibliotecas que importan useState, useEffect o browser APIs fallan en Server Components. Solución: Importalas solo dentro de archivos "use client", o usa un componente wrapper.
| Enfoque | Úsalo Cuando | No lo Úses Cuando |
|---|---|---|
| Server Components | Interfaz de solo lectura, obtención de datos, renderizado sin-JS | Interfaz interactiva con estado o efectos |
| Client Components | Interfaz interactiva con hooks, eventos, browser APIs | Visualización de datos puros sin interactividad |
| Renderizado del lado del servidor (SSR) | Aplicaciones pre-RSC heredadas que necesitan HTML del servidor | Tienes acceso al App Router |
| Generación de Sitios Estáticos | El contenido rara vez cambia y puede compilarse en el tiempo de despliegue | Los datos son específicos del usuario o altamente dinámicos |
| Rutas API + client fetch | Los consumidores externos necesitan un endpoint REST | Los datos solo son consumidos por tus propias páginas |
De una aplicación SaaS de Next.js 15 / React 19 en producción (SystemsArchitect.io).
// Ejemplo de producción: Página de categoría de FAQ con acceso directo a datos
// Archivo: src/app/faqs/[slug]/page.tsx
export default async function FaqCategoryPage({ params }: FaqCategoryPageProps) {
const { slug } = await params;
const category = await getFaqCategory(slug);
if (!category) {
notFound();
}
const iconConfig = getFaqIcon(category.slug);
const IconComponent = iconConfig.icon;
return (
<div className="min-h-screen bg-zinc-50 dark:bg-black">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<Link href="/faqs" className="inline-flex items-center gap-2 text-sm cursor-pointer">
<ChevronLeft className="h-4 w-4" />
<span>Volver a todas las FAQs</span>
</Link>
<div className="flex items-center gap-3 mb-4">
<div className={iconConfig.color}>
<IconComponent className="h-8 w-8" />
</div>
<h1 className="text-3xl font-bold">{category.title}</h1>
</div>
<FaqList faqs={category.faqs} categorySlug={category.slug} />
</div>
</div>
);
}Lo que esto demuestra en producción:
async y directamente awaits datos de getFaqCategory() que llama a Prisma bajo el capóawait params es el patrón Next.js 15+ donde params ahora es una Promise en rutas dinámicasnotFound() de next/navigation dispara el límite not-found.tsx más cercanoFaqList (un Client Component) recibe datos pre-obtenidos como props. El límite servidor-cliente está a nivel de propLos componentes son Server Components de forma predeterminada en el App Router. No se necesita directiva. Se convierten en Client Components solo cuando añades "use client" al archivo.
Sí. Los Server Components pueden ser funciones async y usar await directamente en el cuerpo de la función. Esto no está permitido en Client Components.
No. Los Server Components producen un payload RSC (árbol React serializado) que se transmite al cliente. Las dependencias usadas solo en Server Components (ej. date-fns, analizadores markdown) no se incluyen en el bundle del cliente.
Los Server Components no pueden usar React hooks (useState, useEffect, useRef, etc.) porque se ejecutan en el servidor y no se vuelven a renderizar en el cliente. Extrae partes interactivas en un componente "use client".
La biblioteca probablemente importa useState, useEffect o browser APIs internamente. Importala solo dentro de un archivo "use client", o crea un componente Client Component wrapper delgado alrededor de ella.
Solo datos serializables: strings, números, booleanos, arrays, objetos simples y Server Actions. No puedes pasar funciones regulares, instancias de clases, Symbols o nodos DOM.
Usa el paquete server-only:
import "server-only";
export function getSecretConfig() {
return { apiKey: process.env.SECRET_API_KEY! };
}Esto lanza un error de compilación si el archivo se importa en un archivo "use client".
Usa Promise.all para ejecutar múltiples búsquedas concurrentemente:
const [users, revenue, orders] = await Promise.all([
fetchUsers(),
fetchRevenue(),
fetchOrders(),
]);Los Server Components no se vuelven a renderizar en respuesta a cambios de estado del lado del cliente. Se vuelven a ejecutar solo cuando la ruta cambia o cuando se llama a revalidatePath/revalidateTag.
async function MyComponent(): Promise<JSX.Element> {
const data = await fetchData();
return <div>{data.name}</div>;
}Los tipos de React 19+ manejan Promise<JSX.Element> para componentes async.
Define un tipo con solo campos serializables:
type SerializableProps = {
name: string;
count: number;
items: { id: string; label: string }[];
// NO permitido: onClick: () => void
};"use client"Revisado por Chris St. John·Última actualización: 19 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥