Saltar al contenido

Server Components: dónde está el límite

¿Qué separa exactamente un componente de servidor de uno de cliente?

Al terminar vas a poder explicar

  • Explicar qué significa realmente la directiva use client
  • Predecir qué puede y qué no puede cruzar el límite servidor-cliente
  • Pasar un Server Component como children de uno de cliente y decir por qué funciona

Hay una frase que se repite y que está mal: «use client marca los componentes que corren en el cliente». La directiva no marca eso. Marca dónde empieza el cliente.

Todo lo que se importe desde un módulo marcado con use client — y todo lo que esos módulos importen — entra al bundle del navegador. Es una frontera, no una etiqueta.

Dos mundos con reglas distintas

lo que puede cada lado
1Server Component                    Client Component2────────────────────────────────    ────────────────────────────────3leer la base de datos             ✗ (no tiene acceso)4leer el sistema de archivos5usar secretos y claves            ✗ (irían al navegador)6async/await en el cuerpo7useState, useEffect8onClick y demás manejadores9APIs del navegador10contexto (crear un Provider)      ✓11 120 KB de JavaScript al cliente       pesa lo que pesa

Un Server Component no tiene estado ni efectos porque no vuelve a ejecutarse. Corre una vez, produce su salida, y esa salida viaja. No hay nada que actualizar del lado del cliente porque el componente ya no existe ahí.

Lo que cruza tiene que ser serializable

Esta única restricción explica casi todos los errores que vas a ver. Las props que un Server Component le pasa a uno de cliente viajan por la red, así que tienen que poder serializarse.

1// ✓ Cruzan: primitivos, objetos y arrays planos, Date, Map, Set,2//   promesas, y elementos de React ya construidos.3<Cliente titulo="Hola" datos={{ id: 1 }} fecha={new Date()} />4 5// ✗ No cruzan: funciones, clases, símbolos.6<Cliente alFormatear={(x) => x.toUpperCase()} />   // Error de serialización

La excepción son las Server Actions: funciones marcadas con "use server" que sí cruzan, pero no viaja el código — viaja una referencia. El cliente la invoca y la ejecución ocurre en el servidor.

El patrón que desbloquea todo

La pregunta que todo el mundo se hace: «si un componente de cliente no puede importar uno de servidor, ¿cómo pongo contenido del servidor adentro de un layout interactivo?»

Con composición — la misma herramienta de la lección de prop drilling. Como children es una prop, y los elementos de React ya construidos se serializan, el padre de servidor arma el hijo y lo pasa por el hueco:

1// ✗ Un componente de cliente no puede importar uno de servidor.2"use client";3import ContenidoDelServidor from "./ContenidoDelServidor";   // se rompe4 5// ✓ Recibe el elemento ya construido por el servidor.6"use client";7export function Acordeon({ children }) {8  const [abierto, setAbierto] = useState(false);9  return (10    <div>11      <button onClick={() => setAbierto(!abierto)}>alternar</button>12      {abierto && children}     {/* ← contenido de servidor, ya renderizado */}13    </div>14  );15}16 17// En el Server Component padre:18<Acordeon>19  <ContenidoDelServidor />      {/* se renderiza en el servidor */}20</Acordeon>

Poné el límite lo más abajo posible

1// ✗ Toda la página al bundle por un botón.2"use client";3export default function Pagina() {4  return (5    <article>6      <ArticuloEnorme />          {/* podría ser de servidor */}7      <BotonMeGusta />            {/* lo único que necesita interactividad */}8    </article>9  );10}11 12// ✓ Sólo el botón cruza.13export default function Pagina() {      // Server Component14  return (15    <article>16      <ArticuloEnorme />17      <BotonMeGusta />                   {/* este sí tiene "use client" */}18    </article>19  );20}

Cada use client puesto demasiado arriba arrastra todo el subárbol al navegador. Bajarlo un nivel puede sacar cientos de kilobytes del bundle sin cambiar una línea de lógica.

Antes de seguir · predecí

Un componente de cliente necesita mostrar datos de la base. ¿Cuál es el camino correcto?

Lo que te llevás

use client marca dónde empieza el cliente, no dónde corre algo. Los Server Components no tienen estado porque no vuelven a ejecutarse. Todo lo que cruza el límite tiene que ser serializable — y children es la puerta que hace que el modelo entero funcione.

Antes de marcarla, comprobá

  • ¿Podés explicar explicar qué significa realmente la directiva use client?
  • ¿Podés explicar predecir qué puede y qué no puede cruzar el límite servidor-cliente?
  • ¿Podés explicar pasar un Server Component como children de uno de cliente y decir por qué funciona?