Obtención de Datos en React Server Components
Obtén datos en React Server Components con funciones async, entiende el caché de Next.js 15/16 y evita waterfalls con Promise.all.
En un React Server Component, los datos se obtienen convirtiendo el componente en una función async y usando await directamente en el cuerpo de la función — sin useEffect, sin estado de carga y sin llamadas de API desde el cliente. Este es el modelo predeterminado en el App Router de Next.js, e invierte dos hábitos heredados del React del lado del cliente: la obtención de datos se traslada al renderizado, y el mayor riesgo es el caché, cuyo comportamiento cambió en Next.js 15. Este artículo cubre el patrón actual, la realidad del caché en Next.js 16, por qué "use server" no tiene nada que ver con los Server Components, y cómo evitar las cascadas de solicitudes.
Puntos Clave
- En un Server Component, los datos se obtienen convirtiendo el componente en
asyncy usandoawaiten la solicitud; dado que los Server Components se renderizan en el servidor, las credenciales y la lógica de consulta no se incluyen en el bundle del cliente, por lo que puedes consultar una base de datos directamente con un ORM. - Por defecto, las solicitudes
fetchno se almacenan en caché en las versiones actuales de Next.js — lo opuesto al comportamiento de Next.js 13/14 que aún describen muchos tutoriales. - Para usar caché, debes activarlo explícitamente:
{ cache: 'force-cache' }o{ next: { revalidate } }en el modelo anterior, o la directivause cacheuna vez que se establececacheComponents: true. - No existe ninguna directiva para los Server Components; la directiva “use server” se utiliza para las Server Functions (Server Actions), una característica independiente.
- Inicia las solicitudes independientes sin
awaity resuélvelas conPromise.allpara evitar una cascada secuencial.
¿Cómo se obtienen datos en un Server Component?
El patrón central consiste en un único cambio: convertir el componente en una función async y usar await en la solicitud. Los Server Components son el tipo predeterminado para layouts y páginas en el App Router, por lo que no se necesita ninguna directiva para obtener este comportamiento — los componentes async son una característica de los Server Components que permiten usar await durante el renderizado.
// app/blog/page.tsx
export default async function Page() {
const res = await fetch('https://api.vercel.app/blog')
const posts = await res.json()
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
Dado que el código se ejecuta únicamente en el servidor, puedes omitir la capa de API y consultar tu fuente de datos directamente. Es posible realizar consultas a la base de datos de forma segura usando un ORM o un cliente de base de datos, aunque igualmente debes asegurarte de que las solicitudes estén correctamente autenticadas y autorizadas.
// app/blog/page.tsx
import { db, posts } from '@/lib/db'
export default async function Page() {
const allPosts = await db.select().from(posts)
return (
<ul>
{allPosts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
Compara esto con el patrón del lado del cliente que estás reemplazando: obtener datos en un useEffect, almacenar el resultado en useState y enviar la lógica de obtención (junto con posibles secretos) al navegador. Ese enfoque genera un viaje de ida y vuelta adicional entre cliente y servidor después de que la página carga; usar await en el servidor lo elimina por completo.
Discover how at OpenReplay.com.
fetch no se almacena en caché por defecto — el cambio en Next.js 15/16
En las versiones actuales de Next.js, fetch no se almacena en caché por defecto — vale la pena afirmarlo claramente, ya que la mayoría del contenido más antiguo dice lo contrario. Según la referencia actual de fetch en Next.js, el comportamiento predeterminado es auto no cache: Next.js obtiene el recurso del servidor remoto en cada solicitud. El modelo de caché anterior se comportaba de la misma manera — por defecto, las solicitudes fetch no se almacenan en caché, y para cachear una solicitud individual se debe establecer la opción cache en 'force-cache'. En Next.js 13/14, un fetch sin opciones adicionales se almacenaba en caché (force-cache) por defecto, por lo que cualquier tutorial que dependa de ese comportamiento está desactualizado.
Para usar caché con el modelo anterior, actívalo por solicitud:
// Caché hasta revalidación manual
await fetch('https://api.example.com/posts', { cache: 'force-cache' })
// Sirve datos en caché, revalida como máximo cada 3600 segundos
await fetch('https://api.example.com/posts', { next: { revalidate: 3600 } })
Next.js 16 también incluye un segundo modelo, Cache Components, cuya activación se realiza mediante la directiva use cache. El punto que suele confundir a los desarrolladores: use cache es una característica de Cache Components; para habilitarla, añade la opción cacheComponents en tu archivo next.config.ts. Sin esa opción, use cache no tiene efecto y se recurre a las opciones de fetch mencionadas anteriormente.
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = { cacheComponents: true }
export default nextConfig
import { cacheLife } from 'next/cache'
export async function getPosts() {
'use cache'
cacheLife('hours')
const res = await fetch('https://api.example.com/posts')
return res.json()
}
Aquí, la directiva use cache almacena en caché el valor de retorno de funciones y componentes async, y cacheLife() define la duración. Ten en cuenta que unstable_cache es reemplazado por la directiva use cache en Next.js 16 — no lo uses en código nuevo.
| Cache Components DESACTIVADO (modelo anterior) | Cache Components ACTIVADO (cacheComponents: true) | |
|---|---|---|
fetch por defecto | Sin caché | Sin caché |
| Cachear un fetch | { cache: 'force-cache' } | Directiva use cache |
| Revalidación basada en tiempo | { next: { revalidate: n } } | Perfil cacheLife() |
| Cachear datos sin fetch | React.cache / configuración de ruta | use cache en la función |
Dos comportamientos son independientes del caché. Primero, las solicitudes fetch con GET que usan la misma URL y opciones se memorizan automáticamente durante un ciclo de renderizado en el servidor, de modo que si llamas al mismo fetch en múltiples componentes, Next.js lo ejecuta una sola vez y comparte el resultado. Segundo, para fuentes que no usan fetch, envuelve la llamada en cache() de React; React.cache tiene alcance únicamente a la solicitud actual — cada solicitud obtiene su propio ámbito de memorización sin compartir entre solicitudes.
”use server” no convierte un componente en Server Component
Si hay algo que desorienta a quienes se inician en los RSC, es esta directiva. Un malentendido común es que los Server Components se identifican con "use server", pero no existe ninguna directiva para los Server Components; la directiva "use server" se utiliza para las Server Functions. Los Server Components son simplemente el tipo predeterminado en el App Router — un archivo sin ninguna directiva ya es uno. "use server" marca las Server Functions (Server Actions), y según la referencia de React, las Server Functions están diseñadas para mutaciones que actualizan el estado del lado del servidor; no se recomiendan para la obtención de datos. Usa Server Components async simples para leer datos; usa "use server" para escribirlos.
Evita las cascadas y transmite datos lentos
Usar await en solicitudes una tras otra crea una cascada secuencial. Dentro de cualquier componente, múltiples solicitudes async/await pueden seguir siendo secuenciales si se colocan una después de la otra; inicia múltiples solicitudes llamando a fetch y resuélvelas con Promise.all. Llamar a las funciones sin await las inicia de inmediato:
export default async function Page({ params }: { params: Promise<{ username: string }> }) {
const { username } = await params
const artistData = getArtist(username) // inicia ahora
const albumsData = getAlbums(username) // inicia ahora
const [artist, albums] = await Promise.all([artistData, albumsData])
return <h1>{artist.name}</h1>
}
Para datos genuinamente lentos, no bloquees la ruta. Si tienes solicitudes de datos lentas, toda la ruta queda bloqueada hasta que se obtienen todos los datos; para mejorar el tiempo de carga, divide la página en fragmentos y envíalos progresivamente al cliente. Envuelve el subárbol lento en <Suspense> (o añade un loading.js), y para datos de menor prioridad, inicia la promesa en el servidor y léela en el cliente con la API use:
// Server Component — nota: commentsPromise NO usa await
import { Suspense } from 'react'
import Comments from './comments'
export default async function Page({ id }: { id: string }) {
const commentsPromise = getComments(id)
return (
<Suspense fallback={<p>Cargando comentarios…</p>}>
<Comments commentsPromise={commentsPromise} />
</Suspense>
)
}
// Client Component
'use client'
import { use } from 'react'
export default function Comments({ commentsPromise }: { commentsPromise: Promise<Comment[]> }) {
const comments = use(commentsPromise)
return comments.map((c) => <p key={c.id}>{c.text}</p>)
}
La promesa se inicia en el servidor y se espera en el cliente con la API use; dado que los componentes async no son compatibles en el cliente, la promesa se espera con use. Cuando algo falla, el error solo es visible donde está el usuario — un fallback de Suspense que nunca resuelve su contenido, o un fragmento transmitido que llega después de la hidratación — y una reproducción de sesión es lo que permite reconstruir exactamente lo que el navegador renderizó en ese momento.
Mantén los secretos en el lado del servidor
Los datos obtenidos en el servidor se transfieren a los Client Components como props, y esas props deben ser serializables — pasa datos simples, no funciones ni instancias de clases. Los secretos permanecen en el servidor por defecto porque el código de los Server Components nunca se envía al cliente, pero los módulos compartidos pueden provocar filtraciones. En Next.js, solo las variables de entorno con el prefijo NEXT_PUBLIC_ se incluyen en el bundle del cliente; si las variables no tienen ese prefijo, Next.js las reemplaza con una cadena vacía. Para proteger un módulo que contiene secretos, importa el paquete server-only al inicio del archivo, de modo que una importación accidental desde el cliente falle en tiempo de compilación. React Context tampoco puede usarse en un Server Component — envuélvelo en un proveedor con 'use client' y renderiza ese proveedor dentro de tu layout del servidor.
El cambio conceptual es pequeño pero completo: traslada el fetch a un componente async, usa await, y trata el caché como algo que activas deliberadamente en lugar de algo que ocurre de forma automática. Comienza migrando un fetch de useEffect a un Server Component async, confirma que la solicitud se ejecuta en el servidor y luego decide por ruta si esos datos deben almacenarse en caché con { next: { revalidate } } o con la directiva use cache.
Preguntas Frecuentes
¿Se almacena fetch en caché por defecto en Next.js 16?
No. A partir de Next.js 15 y continuando en la versión 16, fetch no se almacena en caché por defecto y accede al origen en cada solicitud. Esto es lo opuesto a Next.js 13/14, donde un fetch sin opciones adicionales se almacenaba en caché con force-cache por defecto. Para usar caché con el modelo anterior, debes activarlo por solicitud estableciendo cache en force-cache o especificando un valor de revalidate en next; los tutoriales más antiguos que afirman que el caché está activado por defecto están desactualizados.
¿Cuál es la diferencia entre use cache y next revalidate para cachear un fetch?
Pertenecen a dos modelos distintos. La opción next revalidate y force-cache funcionan con el modelo de caché anterior y predeterminado, sin necesidad de configuración adicional. La directiva use cache es una característica de Cache Components que solo tiene efecto cuando cacheComponents está establecido en true en next.config.ts, y su duración se controla con cacheLife. Sin esa opción, use cache no tiene ningún efecto, por lo que un fetch que pretendías cachear accederá silenciosamente al origen en cada solicitud.
¿Puedo usar useEffect para obtener datos en un Server Component?
No. useEffect solo se ejecuta en el navegador, por lo que no puede ejecutarse dentro de un Server Component, que se renderiza únicamente en el servidor. En un Server Component, conviertes el componente en async y usas await en la solicitud directamente durante el renderizado, sin estado de carga y sin viajes de ida y vuelta al cliente. Si necesitas obtener datos en el lado del cliente, añade la directiva use client para convertirlo en un Client Component, o inicia la promesa en el servidor y léela en el cliente con la API use de React.
¿La directiva use server convierte un componente en Server Component?
No. No existe ninguna directiva para los Server Components; son simplemente el tipo predeterminado en el App Router, por lo que un archivo sin ninguna directiva ya es uno. La directiva use server marca las Server Functions, también llamadas Server Actions, que están diseñadas para mutaciones que actualizan el estado del lado del servidor y no se recomiendan para la obtención de datos. Usa un Server Component async simple para leer datos y use server para escribirlos.
¿Qué ocurre si una solicitud falla al usar Promise.all para solicitudes en paralelo?
Promise.all rechaza en cuanto cualquier solicitud individual falla, descartando los resultados de las demás y haciendo fallar todo el renderizado. Si una solicitud debe poder fallar sin afectar al resto, usa Promise.allSettled en su lugar, que resuelve con el estado de cada solicitud y permite manejar los fallos de forma individual. Inicia los fetches sin await para ejecutarlos en paralelo y luego elige entre Promise.all o Promise.allSettled según cómo quieras gestionar los fallos.
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.
Star on GitHub12k