Errores Comunes con React Server Components
Has adoptado el App Router de Next.js. Los Server Components son la opción predeterminada. Todo debería ser más rápido, más simple, más eficiente.
En cambio, estás depurando errores de hidratación a las 2 AM, preguntándote por qué tu bundle creció, y cuestionando si esa directiva 'use client' pertenece donde la colocaste.
React Server Components representa un cambio fundamental en cómo funcionan las aplicaciones React. El límite servidor/cliente ya no es solo una preocupación de despliegue—es una decisión arquitectónica que tomas con cada componente. Equivocarse en esto conduce a bugs sutiles, regresiones de rendimiento y código que lucha contra el framework en lugar de aprovecharlo.
Este artículo cubre los errores más comunes de React Server Components que he visto en bases de código de producción y cómo evitarlos.
Puntos Clave
- Los Server Components son la opción predeterminada en Next.js App Router—empuja
'use client'lo más abajo posible en el árbol de componentes para minimizar el tamaño del bundle. - Usa el paquete
server-onlypara prevenir la exposición accidental de código sensible del servidor al bundle del cliente. - Siempre convierte valores no serializables (como funciones e instancias de clase) antes de pasarlos de Server a Client Components.
- Las Server Actions (
'use server') son endpoints estilo RPC, no Server Components—valida todas las entradas y nunca confíes en los datos del cliente. - Sé explícito sobre el comportamiento de caché con
revalidateocache: 'no-store'ya que los valores predeterminados de Next.js han cambiado entre versiones.
Entendiendo Server vs Client Components
Antes de profundizar en los errores, establezcamos la línea base. En el App Router, los componentes son Server Components por defecto. Se ejecutan en el servidor, no tienen acceso a las APIs del navegador y envían cero JavaScript al cliente.
Los Client Components requieren la directiva 'use client'. Pueden usar hooks como useState y useEffect, acceder a las APIs del navegador y manejar interacciones del usuario.
El límite entre ellos es donde ocurren la mayoría de los errores.
Uso Excesivo de la Directiva ‘use client’
El error más frecuente con RSC del App Router de Next.js es recurrir a 'use client' demasiado pronto. ¿Un componente necesita useState? Márcalo como client component. ¿Necesitas un manejador onClick? Client component.
El problema: 'use client' crea un límite. Todo lo que ese componente importa se convierte en parte del bundle del cliente, incluso si esas importaciones podrían haberse quedado en el servidor.
// ❌ La página entera se convierte en client component
'use client'
import { useState } from 'react'
export default function ProductPage({ product }) {
const [quantity, setQuantity] = useState(1)
return (
<div>
<ProductDetails product={product} />
<ProductReviews productId={product.id} />
<QuantitySelector value={quantity} onChange={setQuantity} />
</div>
)
}
// ✅ Solo la pieza interactiva es un client component
import ProductDetails from './ProductDetails'
import ProductReviews from './ProductReviews'
import QuantitySelector from './QuantitySelector'
export default function ProductPage({ product }) {
return (
<div>
<ProductDetails product={product} />
<ProductReviews productId={product.id} />
<QuantitySelector /> {/* Este es el único client component */}
</div>
)
}
Empuja 'use client' lo más abajo posible en el árbol de componentes. Aísla la interactividad en los componentes más pequeños que la necesiten.
Importar Código Exclusivo del Servidor en Client Components
Cuando un client component importa un módulo, ese módulo completo (y sus dependencias) se envía al navegador. ¿Importas un cliente de base de datos o un archivo que lee secretos de entorno? Acabas de exponer código exclusivo del servidor al grafo del cliente.
// lib/db.js
import 'server-only' // Agrega esto para prevenir importaciones accidentales del cliente
export async function getUsers() {
return db.query('SELECT * FROM users')
}
El paquete server-only (proporcionado por Next.js) causa un error de compilación si el módulo se importa en un client component. Úsalo para cualquier código que nunca deba llegar al navegador.
Discover how at OpenReplay.com.
Pasar Valores No Serializables a Través del Límite
Los Server Components pasan props a los Client Components mediante serialización. Las funciones, instancias de clase, Map y Set no pueden cruzar este límite.
// ❌ Las instancias de clase no son serializables
export default async function UserProfile({ userId }) {
const user = await getUser(userId)
return <ClientProfile user={user} /> // user es una instancia de clase
}
// ✅ Convierte a un objeto plano
export default async function UserProfile({ userId }) {
const user = await getUser(userId)
return (
<ClientProfile
user={{
id: user.id,
name: user.name,
createdAt: user.createdAt.toISOString()
}}
/>
)
}
Malentender las React Server Actions
La directiva 'use server' marca funciones como Server Actions—invocables desde el cliente pero ejecutadas en el servidor. No convierte un componente en Server Component. Los Server Components no necesitan ninguna directiva ya que son la opción predeterminada.
// Esto es una Server Action, no un Server Component
async function submitForm(formData) {
'use server'
await db.insert({ email: formData.get('email') })
}
Las Server Actions son efectivamente endpoints estilo RPC. Trátalas como rutas API: valida las entradas, maneja errores y nunca confíes en los datos del cliente.
Ignorar el Modelo de Caché de RSC
El comportamiento de caché de Next.js ha evolucionado significativamente. No asumas que las llamadas fetch están en caché por defecto—esto varía según la versión de Next.js, la configuración del segmento de ruta y la configuración del runtime. Sé explícito sobre la frescura de los datos.
// Sé explícito sobre las intenciones de caché
const data = await fetch(url, {
next: { revalidate: 3600 } // Cachear por 1 hora
})
// O desactívalo completamente
const data = await fetch(url, { cache: 'no-store' })
Usa revalidatePath() y revalidateTag() en Server Actions para invalidar datos en caché después de mutaciones. El modelo de caché de RSC requiere decisiones intencionales sobre la frescura de los datos.
Conclusión
React Server Components recompensa el pensamiento cuidadoso sobre dónde se ejecuta el código. Usa Server Components por defecto. Empuja los límites del cliente hacia abajo. Serializa datos en el borde. Valida las entradas de Server Actions. Sé explícito sobre el caché.
El modelo mental toma tiempo para internalizarse, pero la recompensa—bundles más pequeños, cargas más rápidas, obtención de datos más simple—vale la inversión.
Preguntas Frecuentes
Parcialmente. Los Server Components no pueden usar hooks de estado o efecto como useState o useEffect porque se ejecutan solo en el servidor. Sin embargo, hooks como useContext sí están soportados. Si tu componente necesita estado, efectos o APIs del navegador, debes agregar la directiva use client para convertirlo en Client Component. Mantén estas piezas interactivas lo más pequeñas y aisladas posible.
Pregúntate si el componente necesita interactividad, APIs del navegador o hooks de React como useState o useEffect. Si la respuesta es sí, debe ser un Client Component. Si solo renderiza datos u obtiene información de una base de datos, mantenlo como Server Component. En caso de duda, comienza con un Server Component y solo agrega use client cuando la compilación o el runtime lo requieran explícitamente.
La causa más común es colocar use client demasiado alto en tu árbol de componentes. Cuando un componente padre se convierte en Client Component, todas sus importaciones se unen al bundle del cliente. Audita tus directivas use client y empújalas hacia abajo a los componentes interactivos más pequeños. También verifica importaciones accidentales de librerías exclusivas del servidor en código del cliente.
La directiva use client marca un componente para ejecutarse en el navegador con acceso a hooks y APIs del navegador. La directiva use server marca una función como Server Action, que es invocable desde el cliente pero se ejecuta en el servidor. Los Server Components no necesitan ninguna directiva ya que son la opción predeterminada en el App Router de Next.js.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.