React Server Components y límites cliente-servidor | Nicolás Garzón
React Server Components permiten ejecutar ciertos componentes en un entorno de servidor y enviar al cliente una representación del árbol, no el código JavaScript de esos componentes. Requieren infraestructura compatible; instalar React por sí solo no activa esta arquitectura.
En una aplicación tradicional todo el árbol de componentes puede formar parte del bundle cliente, incluso cuando algunos componentes solo consultan datos y generan contenido estático.
Server Components separan capacidades:
Texto
Copiar Server Component
→ acceso a datos y recursos del servidor
→ composición y transformación
→ no envía su implementación al bundle cliente
Client Component
→ estado e interacción
→ event handlers y effects
→ APIs del navegador
→ JavaScript enviado al clienteEl objetivo no es mover “todo al servidor”, sino colocar cada responsabilidad en el entorno adecuado.
React define el modelo y el protocolo, pero una aplicación necesita tooling que coordine:
Compilación de módulos servidor y cliente.
Transporte de la representación RSC.
Resolución de referencias a Client Components.
Routing y navegación.
Caché y data fetching.
Server rendering y hydration cuando correspondan.
Despliegue del runtime servidor.
Por eso Server Components se utilizan normalmente mediante frameworks compatibles. No son una opción que se habilita con una prop de createRoot.
La sintaxis concreta depende del framework, pero conceptualmente un Server Component puede ser asíncrono:
TypeScript
Copiar type ProductPageProps = {
productId: string ;
} ;
async function ProductPage ( { productId } : ProductPageProps) {
const product = await productRepository. findById ( productId) ;
if ( ! product) {
return < p> Product not found. < / p> ;
}
return (
< article>
< h1> { product. name} < / h1>
< p> { product. description} < / p>
< ProductActions productId= { product. id} / >
< / article>
) ;
} ProductPage puede acceder al repositorio porque se ejecuta en el servidor. ProductActions debe pertenecer al cliente si maneja estado, eventos o APIs del navegador.
Un límite cliente suele declararse mediante una directiva reconocida por el bundler:
TypeScript
Copiar "use client" ;
import { useState } from "react" ;
export function ProductActions ( { productId } : { productId: string } ) {
const [ quantity, setQuantity] = useState ( 1 ) ;
return (
< section>
< label htmlFor= "quantity" > Cantidad< / label>
< input
id= "quantity"
type= "number"
min= { 1 }
value= { quantity}
onChange= { ( event) => setQuantity ( Number ( event. target. value) ) }
/ >
< button type= "button" onClick= { ( ) => addToCart ( productId, quantity) } >
Agregar al carrito
< / button>
< / section>
) ;
} "use client" marca una frontera de módulos. No significa necesariamente que el componente solo produzca contenido en el navegador.
Dependiendo del framework, un Client Component puede participar en server rendering para generar HTML inicial y después hidratarse.
Son conceptos diferentes:
Texto
Copiar Server rendering
→ ejecuta componentes para producir HTML inicial
→ el cliente hidrata el árbol interactivo
Server Components
→ ejecuta componentes que no se envían como código al cliente
→ produce una representación que puede combinarse con Client ComponentsUn Client Component puede ser renderizado inicialmente en el servidor.
Un Server Component no se hidrata como componente interactivo porque su implementación no forma parte del bundle cliente.
Una API route expone un contrato de red:
Texto
Copiar client
↓ HTTP/JSON
API route
↓
databaseUn Server Component participa en la composición de UI:
Texto
Copiar Server Component
↓ representación RSC
árbol de interfazNo reemplaza todos los endpoints. APIs continúan siendo necesarias para clientes externos, webhooks, integraciones o arquitecturas separadas.
Un Server Component puede renderizar un Client Component:
TypeScript
Copiar async function Dashboard ( ) {
const summary = await getDashboardSummary ( ) ;
return < InteractiveChart initialData= { summary. chartData} / > ;
} También puede pasar Server Components como children a un Client Component:
TypeScript
Copiar < ClientModal trigger= { < OpenButton / > } >
< ServerRenderedProductDetails / >
< / ClientModal> El Client Component recibe una referencia serializable al contenido ya compuesto. No importa directamente la implementación privada del servidor.
Un Client Component no debe importar módulos exclusivos del servidor:
TypeScript
Copiar "use client" ;
import { database } from "./database" ; Eso podría exponer código sensible o fallar durante compilación.
La dirección segura suele ser:
Texto
Copiar Server Component
↓ imports / renders
Client ComponentEl cliente puede recibir props serializables, pero no arrastrar dependencias privadas del servidor.
Los valores que cruzan el límite deben pertenecer al formato soportado por React y el framework.
Ejemplos normalmente adecuados:
Strings.
Números.
Booleanos.
null.
Arrays y objetos serializables.
Elementos y referencias reconocidas por React.
No pases arbitrariamente:
Conexiones de base de datos.
Sockets.
Instancias de clases de dominio sin contrato de serialización.
Funciones cliente ordinarias creadas en servidor.
Objetos con secretos.
TypeScript
Copiar
< ClientComponent database= { databaseConnection} / > TypeScript
Copiar const productView = {
id: product. id,
name: product. name,
formattedPrice: formatPrice ( product. price) ,
} ;
return < ProductActions product= { productView} / > ; Server Components no conservan estado interactivo entre renders del navegador y no utilizan hooks exclusivos del cliente como:
useState.
useEffect.
useLayoutEffect.
useReducer para interacción local.
Pueden utilizar capacidades compatibles con el entorno servidor y composición permitida por el framework.
No conviertas un componente en cliente solo para usar un hook que en realidad no necesitas.
Una ventaja es consultar datos cerca de la UI que los necesita:
TypeScript
Copiar async function OrdersList ( { customerId } : { customerId: string } ) {
const orders = await ordersRepository. listByCustomer ( customerId) ;
if ( orders. length === 0 ) {
return < p> No tienes pedidos. < / p> ;
}
return (
< ul>
{ orders. map ( ( order) => (
< li key= { order. id} > { order. number } < / li>
) ) }
< / ul>
) ;
} Esto puede evitar enviar una capa de fetching al navegador y reducir JavaScript cliente.
Pero todavía debes considerar:
Caché.
Autorización.
Errores.
Waterfalls.
Invalidación.
Latencia entre servicios.
Consistencia después de mutaciones.
Código ejecutado exclusivamente como Server Component no necesita enviarse al navegador.
JavaScript descargado.
Tiempo de parseo y ejecución.
Dependencias cliente innecesarias.
Código de transformación que solo sirve para preparar contenido.
No garantiza automáticamente mejor rendimiento. Un servidor lento, waterfalls o límites cliente mal colocados pueden perjudicar la experiencia.
No marques una página completa como cliente por un botón:
Texto
Copiar ProductPage (server)
├─ ProductInformation (server)
├─ Recommendations (server)
└─ AddToCartButton (client)Tampoco fragmentes cada elemento interactivo en una frontera sin considerar composición y mantenimiento.
La frontera debe corresponder a una región que necesita capacidades cliente.
"use client" no significa:
“Este archivo nunca se ejecuta en servidor”.
“Todo descendiente visual debe ser cliente”.
“Los datos están protegidos porque vienen del servidor”.
Significa que ese módulo y su grafo de dependencias pertenecen al lado cliente del límite.
Una importación cliente puede aumentar el bundle de todos los módulos que arrastra.
"use server" se utiliza en integraciones compatibles para marcar funciones invocables mediante el mecanismo de Server Functions o Actions.
No significa “convertir este componente en Server Component”.
Validar inputs en runtime.
Verificar autenticación y autorización.
Manejar errores.
Ser idempotente cuando el flujo pueda repetirse.
No confiar en datos ocultos por la UI.
La implementación y el transporte dependen del framework.
Ejecutarse en el servidor permite acceder a recursos privados, pero no garantiza seguridad por sí mismo.
TypeScript
Copiar async function deleteProject ( projectId: string , session: Session) {
const project = await getProject ( projectId) ;
if ( ! canDeleteProject ( session. user, project) ) {
throw new Error ( "Forbidden" ) ;
}
await projectRepository. delete ( projectId) ;
} Verifica permisos dentro de cada operación sensible.
No envíes secretos dentro de props serializadas ni mensajes de error detallados al cliente.
React y algunos frameworks ofrecen mecanismos para detectar objetos que no deben atravesar el límite. Estas ayudas complementan, pero no sustituyen:
DTOs explícitos.
Revisión de dependencias.
Autorización.
Clasificación de datos sensibles.
Tests de seguridad.
Server Components se combinan con Suspense para revelar partes del árbol cuando estén listas.
TypeScript
Copiar < Suspense fallback= { < RecommendationsSkeleton / > } >
< Recommendations productId= { product. id} / >
< / Suspense> El framework puede transmitir segmentos progresivamente. Suspense coordina la boundary; el framework coordina red, caché y navegación.
Los errores de Server Components pueden:
Activar una Error Boundary compatible.
Producir una respuesta de servidor fallida.
Ser manejados por una boundary de ruta del framework.
No envíes stack traces ni datos sensibles al cliente en producción.
En una navegación RSC, el framework puede solicitar una nueva representación del servidor y combinarla con el árbol cliente conservando estado donde la identidad permanece.
Esta experiencia depende del router y del protocolo del framework, no de React usado como SPA básica.
Interfaces con mucho contenido derivado de datos del servidor.
Dependencias pesadas que no necesitan ejecutarse en cliente.
Aplicaciones donde reducir JavaScript cliente es importante.
Composición de servidor integrada con routing y caché.
Acceso seguro y cercano a fuentes de datos.
Una SPA pequeña totalmente estática.
Un widget embebido sin backend React.
Una aplicación que ya tiene una arquitectura API separada y no necesita RSC.
Un proyecto cuyo framework o infraestructura no los soporta.
Equipos que todavía no pueden operar el runtime y las fronteras necesarias.
Server Components forman parte del modelo moderno de React, pero su uso práctico depende de frameworks e infraestructura. Las APIs de integración para bundlers y servidores pueden evolucionar de forma independiente al modelo conceptual.
Documenta siempre la versión del framework, porque detalles como caché, Actions, directivas y despliegue no pertenecen únicamente a React.
Confundir Server Components con SSR.
Creer que un Client Component solo se renderiza en el navegador.
Marcar toda una aplicación como cliente por una interacción pequeña.
Importar módulos privados del servidor en código cliente.
Pasar valores no serializables o datos sensibles.
Tratar "use server" como directiva de componente.
Asumir que acceder a la base de datos implementa autorización.
Presentar RSC como una capacidad disponible sin framework.
Convertir detalles específicos de Next.js en reglas universales de React.
Server Components ejecutan código de componentes en el servidor sin enviar su implementación al cliente.
Client Components contienen estado, eventos, effects y APIs del navegador.
Client Component no significa “solo render cliente”.
RSC y SSR resuelven problemas diferentes y pueden combinarse.
Las props que cruzan el límite deben ser serializables y seguras.
"use client" marca un límite de módulos; "use server" marca funciones bajo integraciones compatibles.
Se necesita framework y tooling.
Cada operación sensible debe autorizarse en el servidor.
¿Por qué Server Components no son lo mismo que SSR?
¿Puede un Client Component producir HTML inicial en el servidor?
¿Qué ocurre si un módulo cliente importa una conexión de base de datos?
¿Por qué "use server" no convierte un componente en Server Component?
¿Qué criterio ayuda a decidir una boundary cliente?
Ver respuestas
SSR produce HTML inicial; RSC define componentes cuya implementación permanece en servidor y envía una representación.
Sí. Un framework puede server-renderizarlo y después hidratarlo.
Rompe la frontera y puede fallar o exponer código sensible al bundle cliente.
Porque marca funciones compatibles con el mecanismo de Server Functions o Actions.
La región que realmente necesita estado, eventos, effects o APIs del navegador.
Conecta esta nota con Next.js para estudiar cómo un framework concreto implementa routing, caché, rendering por ruta, Server Components, Actions y despliegue sin confundir esas decisiones con React base.