Adamarant
Iniciar
Volver a Apuntes

React Server Components en producción: 6 fallos a evitar en 2026

Web Design and Engineering8 sept 20267 min de lectura

El 45% de los desarrolladores React ya usa Server Components, pero en el State of React 2025 son la tercera función menos querida. Seis fallos lo explican.

Empty parking lot with white lines on concrete

Un equipo migra una aplicación React al App Router, mueve casi todo el árbol a Server Components y publica. El bundle de cliente crece 40 kB en lugar de bajar. Nadie sabe explicarlo. La causa suele ser una línea: un 'use client' arriba de un layout compartido o de un archivo barrel, que arrastra al navegador cada módulo importado por debajo.

El fallo se repite de equipo en equipo. Los Server Components están disponibles en versión estable desde React 19, publicado en diciembre de 2024, y la adopción existe: en la encuesta State of React 2025 el 45% de quienes respondieron dice haberlos usado. La valoración no acompaña. En esa misma encuesta los Server Components salen como la tercera función menos querida y las Server Functions como la cuarta. El campo estuvo abierto del 19 de noviembre de 2025 al 13 de enero de 2026 y recogió 3.760 respuestas.

La distancia entre adopción y satisfacción tiene una explicación concreta. RSC no es una optimización que se activa: es una frontera dentro del grafo de módulos. Quien lo trata como un interruptor se encuentra con seis problemas recurrentes.

Por qué crece el bundle de cliente al pasar a Server Components

Porque 'use client' marca una frontera, no un componente suelto. La documentación de React lo dice sin rodeos: la directiva declara el punto de entrada del grafo de módulos de cliente, y cada módulo importado desde ese archivo, componentes hijos incluidos, acaba en el bundle del navegador. No hace falta repetirla más abajo en el árbol, y ahí está la trampa. Una sola directiva en un barrel compartido tipo components/index.ts se lleva al navegador el barrel entero.

La directiva va donde de verdad hace falta interactividad: en el botón, no en la página que lo pinta. Los helpers de servidor viven en archivos que nunca cuelgan de un punto de entrada de cliente. Los barrels de las carpetas de UI compartida se eliminan, porque un barrel garantiza que la frontera quede demasiado arriba. Después se comprueba: next build imprime el First Load JS por ruta y tras una migración que sale bien ese número baja. Si sube, la frontera está mal puesta.

Los await en secuencia convierten una página en una cadena de esperas

Con Server Components se escribe await dentro del componente. Se lee bien y esconde un coste. Si un componente padre espera antes de renderizar hijos que a su vez piden datos, las peticiones salen una detrás de otra y el time to first byte pasa a ser la suma de todos los saltos en vez del más lento. LogRocket lo documenta como el fallo de rendimiento más común con RSC en Next.js.

Hay dos arreglos y no son intercambiables. Si las llamadas son independientes, se suben al componente padre y arrancan juntas con Promise.all. Si una es lenta y las demás no, se hace lo contrario: cada fetch va en un componente hermano, envuelto en <Suspense>, y React manda los resultados en streaming según llegan, de forma que una consulta de informes de 900 ms no bloquea una cabecera de 20 ms.

El Context no cruza la frontera, y el atajo deshace la migración

La incompatibilidad con el Context API es la queja más citada en el análisis del State of React 2025, con 59 menciones. El motivo es estructural: un Server Component se renderiza una vez, en el servidor, sin estado y sin efectos, así que useContext, useState y useEffect no están por diseño.

El atajo habitual consiste en envolver la aplicación en un provider de cliente dentro del layout raíz. El Context vuelve a funcionar y, de paso, todo el subárbol vuelve al cliente, lo que anula el motivo de la migración. El Context es un mecanismo de cliente y se queda dentro de los subárboles de cliente. En el servidor los datos bajan como props, los valores ligados a la petición como cookies, cabeceras y parámetros de ruta se leen donde hacen falta, y las lecturas repetidas dentro de una misma petición se deduplican con cache() de React. Tratamos dónde colocar esa frontera en nuestro árbol de decisión entre servidor y cliente.

Los fallos de serialización aparecen en ejecución, no al compilar

Cada prop que pasa de un Server Component a un Client Component se serializa en el payload RSC. Objetos planos, arrays, primitivos, Promises y Server Functions cruzan sin problema. Las instancias de clase y las funciones normales no. Los culpables típicos son los modelos de un ORM, los tipos de los drivers de base de datos como Decimal y los callbacks que se pasan hacia abajo por costumbre.

TypeScript no detecta nada de esto. El error llega a la consola del navegador la primera vez que se renderiza esa rama, es decir en staging o directamente en producción. En la frontera conviene convertir a objetos planos y tiparlos como DTO explícitos, para que la forma de los datos que cruza sea una decisión y no un descuido.

En Next.js 16 el caching cambió bajo los pies

Next.js 16 introdujo los Cache Components. Con cacheComponents: true no se cachea nada por defecto y eliges qué cachear con la directiva 'use cache', que sustituye a unstable_cache y elimina el caching implícito de los fetch. La documentación de caching describe el comportamiento nuevo: los datos leídos en la petición son dinámicos mientras no los marques como cacheados.

Quien aprendió RSC en Next.js de la 13 a la 15 arrastra intuiciones que ya no valen. Páginas que creía estáticas se renderizan en cada petición, los tiempos de respuesta se mueven y el coste de infraestructura se mueve con ellos. Tras la actualización toca revisar ruta por ruta en vez de fiarse del modelo mental anterior, y la revisión se combina con una estructura que envía en streaming. El Partial Prerendering es el mecanismo que hace rentable una shell cacheada de forma explícita.

Los Server Components asíncronos siguen fuera de los tests unitarios

La guía oficial de Next.js sobre Vitest lo dice sin matices: los Server Components asíncronos no están soportados por Vitest y para ellos se recomiendan tests end-to-end. Las carencias de testing sumaron 24 menciones en las respuestas del State of React 2025. Jest tiene el mismo límite.

El reparto que funciona tiene dos niveles. Con tests unitarios se cubren los componentes síncronos, las Server Functions tratadas como funciones normales, la validación de esquemas y la lógica pura. Con Playwright se cubren los Server Components asíncronos, el middleware, las cookies y el enrutado. Quien se salta este paso descubre el hueco cuando la cobertura pinta bien y una regresión de carga de datos llega igualmente a producción.

Cómo es una migración que sale bien

  1. Se empieza midiendo. First Load JS por ruta, LCP y TTFB en las tres rutas con más tráfico. Sin números antes, después no se puede afirmar nada.
  2. Se mueven las hojas, no las raíces. Primero los componentes que solo leen datos. Los subárboles interactivos se quedan quietos hasta que el camino de los datos esté estable.
  3. 'use client' baja todo lo posible. Una directiva por isla interactiva, nunca en un layout ni en un barrel.
  4. La frontera tiene contrato. DTO explícitos para cada prop que cruza a un componente de cliente, así la serialización se revisa en el code review y no en producción.
  5. Paralelo o streaming, se decide ruta a ruta. Promise.all para llamadas independientes, una frontera Suspense cuando una llamada es lenta.
  6. Se vuelve a medir. Si el peso del bundle, el LCP y el TTFB no mejoran, la migración solo ha traído complejidad. Esa ruta se revierte.

La ganancia se concentra en las rutas con mucho contenido e indexadas: páginas de marketing, catálogos, documentación, artículos. Son las rutas donde enviar menos JavaScript se traduce en una página medible más rápida y donde se mueven los Core Web Vitals.

Cuándo no adoptar RSC

Tres casos en los que el cambio no compensa. Un panel detrás de autenticación, muy interactivo y sin presencia en buscadores, gasta el presupuesto de complejidad para muy poco, porque casi todo el subárbol acaba en el cliente de todos modos. Una aplicación pequeña con un backend rápido gana poco moviendo el renderizado a un servidor que ahora tiene que existir y que se paga. Y un equipo sin estrategia de tests para componentes asíncronos, en un producto donde las regresiones cuestan dinero, compra un agujero conocido en su red de seguridad.

RSC funciona bien como punto de partida en aplicaciones nuevas con una parte pública relevante. Funciona mal como punto de partida en una aplicación existente que ya va bien, donde el coste de migrar es real y la ganancia medida ronda el cero. La decisión se toma ruta a ruta, con los números delante, y las rutas donde los números dicen que no se quedan como están.

Foto de Turquo Cabbit en Unsplash

Preguntas frecuentes

¿Los React Server Components hacen que una aplicación sea más rápida por defecto?+

No. Quitan código de componentes del bundle de cliente, lo que ayuda en páginas que enviaban JavaScript innecesario. No arreglan una consulta lenta y añaden un salto al servidor que una página estática no tenía. En rutas públicas con mucho contenido el cambio suele compensar. En un panel interactivo, donde casi todo acaba en el cliente, la ganancia medida ronda el cero. Conviene registrar First Load JS, LCP y TTFB antes de migrar y comparar después, ruta a ruta.

¿Puedo seguir usando Redux o Zustand con Server Components?+

Sí, dentro de subárboles de cliente. Un store es estado de cliente y vive detrás de una frontera "use client" como cualquier otro código interactivo. Lo que cambia es dónde se coloca el provider. Envolver el layout raíz en un provider funciona y devuelve todo el árbol al cliente, lo que anula la ventaja. El provider va alrededor de la isla interactiva que lo necesita, los datos del servidor entran en esa isla como props y el resto de la ruta se queda en el servidor.

¿Conviene migrar al App Router una app Next.js que corre en el Pages Router en 2026?+

Solo ruta a ruta, y solo donde los números lo justifiquen. El Pages Router funciona y sigue recibiendo correcciones. Reescribir entera una aplicación que ya va bien significa comprar un modelo de caching nuevo, un hueco de testing en componentes asíncronos y una categoría nueva de errores de serialización, a cambio de una reducción de bundle que se puede estimar antes. Se empieza por las dos o tres rutas públicas que traen tráfico orgánico, se mide y sobre el resto se decide después.

¿Cómo se detectan los errores de serialización antes de producción?+

Tipando la frontera. Para cada prop que cruza de un Server Component a un Client Component se define un DTO explícito, se construye con una función de mapeo y nunca se pasa directamente una entidad del ORM ni un tipo del driver. Así un fallo invisible en ejecución se convierte en una pregunta de code review. Después se añade una pasada de Playwright sobre las rutas que renderizan esas ramas, porque un test unitario síncrono no ejecuta el render asíncrono de servidor donde aparece el error.

Studio

Empieza un proyecto.

Escribimos sobre lo que construimos. Cuéntanos qué quieres construir tú.