Web Design and Engineering

Server Components vs Client Components: el árbol de decisión que seguimos

El árbol de decisión de cinco pasos que aplicamos en cada proyecto Next.js 16 para decidir cuándo un componente queda en el servidor y cuándo necesita "use client".

20 de abril de 20267 min de lectura
Server Components vs Client Components: el árbol de decisión que seguimos

Al final de esta guía tendrás una regla clara y repetible para decidir cuándo optar por un Server Component y cuándo marcar un archivo con "use client". Es el mismo árbol de decisión que aplicamos en cada proyecto Next.js 16 que enviamos a producción, y elimina la mayoría de los debates "¿dónde va esta lógica?" en la revisión de pull requests.

La pregunta es práctica, no teológica. Next.js 16 y React 19 hacen de los Server Components el valor por defecto, así que cada componente dentro de app/ empieza en el servidor salvo que optes por lo contrario. El error que cometen la mayoría de los equipos es el inverso: marcan toda la página "use client" en cuanto un hijo necesita estado, y entonces envían un bundle de JavaScript que reconstruye todo el árbol en el navegador sin motivo.

Antes de empezar

  • Un proyecto Next.js 16 que usa el App Router (directorio app/).
  • React 19 (instalado por defecto en Next.js 16).
  • Familiaridad con las dos directivas: "use client" y "use server". Esta guía se centra en la primera.

El árbol de decisión en cinco pasos

Aplica los pasos en orden. Detente en el primero cuya respuesta sea sí.

Paso 1. ¿El componente necesita estado, eventos, efectos o APIs del navegador?

Es la única pregunta que la documentación oficial de React trata como regla rígida. Los Client Components son obligatorios cuando usas useState, useEffect, manejadores de eventos tipo onClick, localStorage, window, navigator, o cualquier hook de terceros que envuelva uno de esos. Si la respuesta es sí, pon "use client" al inicio del archivo, sobre los imports, y continúa (documentación de React sobre la directiva).

Si la respuesta es no, por defecto Server Component. No hace falta directiva. Los Server Components son el valor por defecto en Next.js 16 (Next.js server y client components).

Paso 2. ¿Puedes bajar el límite "use client" más abajo en el árbol?

Es el paso que los equipos saltan más a menudo. Cuando un archivo está marcado "use client", cada archivo que importa pasa a formar parte del bundle cliente. Incluye cualquier lógica server-only que hayas arrastrado por descuido. La solución es aislar la pieza interactiva en su propio archivo y mantener el padre en el servidor.

Ejemplo concreto: una página de detalle de producto con un botón "Comprar". La página lee el producto de la base de datos (trabajo de servidor), renderiza la mayor parte del layout (estático) y necesita una sola pieza interactiva para el carrito. El botón es el Client Component. La página, la imagen, la descripción y la tabla de especificaciones se quedan en el servidor. Así el payload de JavaScript se limita al botón y sus dependencias.

Paso 3. ¿La interactividad necesita datos de un Server Component?

No puedes importar un Server Component dentro de un Client Component. El runtime de React es unidireccional: el servidor renderiza primero, el cliente hidrata después. Importar a través del límite forzaría al cliente a volver al servidor, que es precisamente lo que los React Server Components existen para evitar.

El patrón soportado es la composición a través de children u otras props de tipo React-node. Un Server Component padre importa tanto el Client Component como el Server Component, y pasa el Server Component como hijo. El Client Component lo recibe como JSX opaco y decide dónde colocarlo (patrones de composición de Next.js). Usamos esta forma para modales, drawers y tabs que deben alojar contenido renderizado en el servidor.

Paso 4. ¿El componente se usa solo dentro de un subárbol cliente?

Si la respuesta es sí, ya es de hecho un Client Component. Una sola directiva "use client" en el límite convierte todo el subárbol. Añadir la directiva a cada hijo es redundante y ruidoso en la revisión de código. Ponla solo en el punto de entrada donde un Server Component importa al mundo cliente.

Paso 5. Mide antes de migrar.

El ahorro en el bundle al mover trabajo al servidor varía en órdenes de magnitud. Frigade documentó una reducción del 62% en el bundle tras una migración a RSC porque un renderer de markdown grande pasó al servidor (ingeniería de Frigade, 2024). Otras apps ven ganancias de un solo dígito porcentual. La diferencia está en si las librerías que alojas en el cliente pueden realmente ejecutarse en el servidor. Syntax highlighters, parsers de markdown, formateadores de fechas y ayudas de i18n suelen ser portables. Editores de texto enriquecido, librerías de gráficos y de mapas normalmente no.

Antes de prometerle a un equipo una ganancia de rendimiento, ejecuta @next/bundle-analyzer o inspecciona la salida de next build, señala los cinco imports cliente más pesados y revisa si alguno puede pasar al servidor. Si ninguno puede, el árbol de decisión se detiene en su forma actual y el beneficio es pequeño.

Verificar que funciona

  • Panel Network. Los Server Components emiten HTML sin un chunk de JavaScript correspondiente para su contenido. Abre DevTools, recarga con caché fría, y confirma que el HTML inicial contiene tu texto. Si el texto solo aparece tras cargar JavaScript, el componente está en el cliente.
  • Bundle analyzer. Ejecuta ANALYZE=true next build con @next/bundle-analyzer configurado. Las hojas Client Component aparecen como chunks nombrados. Si ves un chunk para algo que debería ser estático, el límite "use client" está más arriba de lo que debería.
  • Ver código fuente. Busca en el HTML devuelto tu texto estático. Presente en el HTML: renderizado en servidor. Solo dentro de una etiqueta <script>: renderizado en cliente.

Errores comunes y soluciones

Error: "Functions cannot be passed directly to Client Components". Estás pasando una prop de tipo función desde un Server Component a un Client Component. Pon la lógica en línea en el cliente, o usa una Server Action (directiva "use server" sobre la función) para que el runtime la transporte a través del límite.

Hydration mismatch en un componente que debería ser estático. Una librería de terceros que importas usa hooks de React internamente, contaminando el archivo como ejecutado en el cliente. Mueve el import detrás de un wrapper Client Component, o sustituye la librería.

Página entera marcada "use client". La señal es un único chunk JavaScript grande para la ruta. Divide la página: mantén page.tsx en el servidor, mueve el bloque interactivo a un archivo hermano, importa el hermano desde la página de servidor.

Providers de contexto en la raíz. Theme providers, query clients y auth providers son Client Components que envuelven toda la app. Está bien. No promueven a sus hijos al cliente salvo que los hijos ya lo sean. Next.js sigue emitiendo HTML de servidor para hijos Server Component envueltos dentro de un provider cliente.

Fetching de datos en un Client Component con useEffect. Es un patrón del Pages Router. En el App Router, haz el fetch en el Server Component, pasa los datos como prop y deja que el Client Component los renderice. Si los datos son lentos, envuelve el componente que hace fetch en <Suspense> y hazle streaming (guía de streaming de Next.js).

Para profundizar

Dos superficies extienden este árbol de forma natural. Para mutaciones de datos, usa Server Actions (directiva "use server" sobre una función async). Para renderizado progresivo de datos de servidor lentos sin bloquear la página, combina Suspense con loading.tsx.

El modelo mental de los Server Components de Josh W. Comeau es el mejor primer externo que hemos leído (Making Sense of React Server Components). Enmarcar los Server Components como salida de build, no como paradigma de runtime, ayuda a los equipos que vienen del mundo SSR-más-hydration de Next.js 13 y anteriores. Si todavía operas dentro de ese modelo, la primera recompensa es conceptual, no de rendimiento.

La regla a la que volvemos en cada proyecto: el valor por defecto es servidor. La excepción está documentada, es local y tan pequeña como puede. Esa regla ha detenido más regresiones de bundle que cualquier lint que hayamos probado.

Foto de Kelly Sikkema en Unsplash

Studio

Empieza un proyecto.

Un partner único para empresas, sector público, startups y SaaS. Producción más rápida, tecnología moderna, costes reducidos. Un equipo, una factura.