Cómo obtener datos y transmitirlos en streaming
Esta página te guiará sobre cómo puedes obtener datos en Componentes de Servidor y Cliente, y cómo transmitir en streaming componentes que dependen de datos.
Obtención de datos
Componentes de Servidor
Puedes obtener datos en Componentes de Servidor usando:
- La API
fetch
- Un ORM o base de datos
Con la API fetch
Para obtener datos con la API fetch
, convierte tu componente en una función asíncrona y espera (await
) la llamada a fetch
. Por ejemplo:
export default async function Page() {
const data = await fetch('https://api.vercel.app/blog')
const posts = await data.json()
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
export default async function Page() {
const data = await fetch('https://api.vercel.app/blog')
const posts = await data.json()
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
Nota importante:
- Las respuestas de
fetch
no se almacenan en caché por defecto. Sin embargo, Next.js prerenderizará la ruta y la salida se almacenará en caché para mejorar el rendimiento. Si deseas optar por el renderizado dinámico, usa la opción{ cache: 'no-store' }
. Consulta la Referencia de la APIfetch
.- Durante el desarrollo, puedes registrar las llamadas a
fetch
para mayor visibilidad y depuración. Consulta la referencia de la APIlogging
.
Con un ORM o base de datos
Dado que los Componentes de Servidor se renderizan en el servidor, puedes hacer consultas a la base de datos de forma segura usando un ORM o cliente de base de datos. Convierte tu componente en una función asíncrona y espera (await
) la llamada:
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>
)
}
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>
)
}
Componentes de Cliente
Hay dos formas de obtener datos en Componentes de Cliente, usando:
- El hook
use
de React - Una biblioteca de la comunidad como SWR o React Query
Transmisión de datos con el hook use
Puedes usar el hook use
de React para transmitir datos desde el servidor al cliente. Comienza obteniendo datos en tu componente de Servidor y pasa la promesa a tu Componente de Cliente como prop:
import Posts from '@/app/ui/posts
import { Suspense } from 'react'
export default function Page() {
// No esperes (await) la función de obtención de datos
const posts = getPosts()
return (
<Suspense fallback={<div>Loading...</div>}>
<Posts posts={posts} />
</Suspense>
)
}
import Posts from '@/app/ui/posts
import { Suspense } from 'react'
export default function Page() {
// No esperes (await) la función de obtención de datos
const posts = getPosts()
return (
<Suspense fallback={<div>Loading...</div>}>
<Posts posts={posts} />
</Suspense>
)
}
Luego, en tu Componente de Cliente, usa el hook use
para leer la promesa:
'use client'
import { use } from 'react'
export default function Posts({
posts,
}: {
posts: Promise<{ id: string; title: string }[]>
}) {
const allPosts = use(posts)
return (
<ul>
{allPosts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
'use client'
import { use } from 'react'
export default function Posts({ posts }) {
const posts = use(posts)
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
En el ejemplo anterior, el componente <Posts>
está envuelto en un límite <Suspense>
. Esto significa que se mostrará el fallback mientras se resuelve la promesa. Aprende más sobre streaming.
Bibliotecas de la comunidad
Puedes usar una biblioteca de la comunidad como SWR o React Query para obtener datos en Componentes de Cliente. Estas bibliotecas tienen sus propias semánticas para caché, streaming y otras características. Por ejemplo, con SWR:
'use client'
import useSWR from 'swr'
const fetcher = (url) => fetch(url).then((r) => r.json())
export default function BlogPage() {
const { data, error, isLoading } = useSWR(
'https://api.vercel.app/blog',
fetcher
)
if (isLoading) return <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>
return (
<ul>
{data.map((post: { id: string; title: string }) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
'use client'
import useSWR from 'swr'
const fetcher = (url) => fetch(url).then((r) => r.json())
export default function BlogPage() {
const { data, error, isLoading } = useSWR(
'https://api.vercel.app/blog',
fetcher
)
if (isLoading) return <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>
return (
<ul>
{data.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
Eliminación de duplicados con React.cache
La eliminación de duplicados es el proceso de evitar solicitudes duplicadas para el mismo recurso durante un pase de renderizado. Te permite obtener los mismos datos en diferentes componentes mientras evitas múltiples solicitudes de red a tu fuente de datos.
Si estás usando fetch
, las solicitudes se pueden deduplicar agregando cache: 'force-cache'
. Esto significa que puedes llamar de forma segura a la misma URL con las mismas opciones, y solo se hará una solicitud.
Si no estás usando fetch
, y en su lugar usas un ORM o base de datos directamente, puedes envolver tu obtención de datos con la función React cache
.
import { cache } from 'react'
import { db, posts, eq } from '@/lib/db'
export const getPost = cache(async (id: string) => {
const post = await db.query.posts.findFirst({
where: eq(posts.id, parseInt(id)),
})
})
import { cache } from 'react'
import { db, posts, eq } from '@/lib/db'
import { notFound } from 'next/navigation'
export const getPost = cache(async (id) => {
const post = await db.query.posts.findFirst({
where: eq(posts.id, parseInt(id)),
})
})
Streaming
Advertencia: El contenido a continuación asume que la opción de configuración
dynamicIO
está habilitada en tu aplicación. Esta bandera se introdujo en Next.js 15 canary.
Cuando usas async/await
en Componentes de Servidor, Next.js optará por el renderizado dinámico. Esto significa que los datos se obtendrán y renderizarán en el servidor para cada solicitud de usuario. Si hay alguna solicitud de datos lenta, toda la ruta se bloqueará para renderizar.
Para mejorar el tiempo de carga inicial y la experiencia del usuario, puedes usar streaming para dividir el HTML de la página en fragmentos más pequeños y enviar progresivamente esos fragmentos desde el servidor al cliente.

Hay dos formas en que puedes implementar streaming en tu aplicación:
- Envolviendo una página con un archivo
loading.js
- Envolviendo un componente con
<Suspense>
Con loading.js
Puedes crear un archivo loading.js
en la misma carpeta que tu página para transmitir toda la página mientras se obtienen los datos. Por ejemplo, para transmitir app/blog/page.js
, agrega el archivo dentro de la carpeta app/blog
.

export default function Loading() {
// Define la UI de carga aquí
return <div>Loading...</div>
}
export default function Loading() {
// Define la UI de carga aquí
return <div>Loading...</div>
}
En la navegación, el usuario verá inmediatamente el diseño y un estado de carga mientras se renderiza la página. El nuevo contenido se intercambiará automáticamente una vez que se complete el renderizado.

Detrás de escena, loading.js
se anidará dentro de layout.js
, y envolverá automáticamente el archivo page.js
y cualquier hijo debajo en un límite <Suspense>
.

Este enfoque funciona bien para segmentos de ruta (diseños y páginas), pero para un streaming más granular, puedes usar <Suspense>
.
Con <Suspense>
<Suspense>
te permite ser más granular sobre qué partes de la página transmitir. Por ejemplo, puedes mostrar inmediatamente cualquier contenido de la página que esté fuera del límite <Suspense>
, y transmitir la lista de publicaciones del blog dentro del límite.
import { Suspense } from 'react'
import BlogList from '@/components/BlogList'
import BlogListSkeleton from '@/components/BlogListSkeleton'
export default function BlogPage() {
return (
<div>
{/* Este contenido se enviará al cliente inmediatamente */}
<header>
<h1>Bienvenido al Blog</h1>
<p>Lee las últimas publicaciones a continuación.</p>
</header>
<main>
{/* Cualquier contenido envuelto en un límite <Suspense> se transmitirá */}
<Suspense fallback={<BlogListSkeleton />}>
<BlogList />
</Suspense>
</main>
</div>
)
}
import { Suspense } from 'react'
import BlogList from '@/components/BlogList'
import BlogListSkeleton from '@/components/BlogListSkeleton'
export default function BlogPage() {
return (
<div>
{/* Este contenido se enviará al cliente inmediatamente */}
<header>
<h1>Bienvenido al Blog</h1>
<p>Lee las últimas publicaciones a continuación.</p>
</header>
<main>
{/* Cualquier contenido envuelto en un límite <Suspense> se transmitirá */}
<Suspense fallback={<BlogListSkeleton />}>
<BlogList />
</Suspense>
</main>
</div>
)
}
Creando estados de carga significativos
Un estado de carga instantáneo es una UI de respaldo que se muestra inmediatamente al usuario después de la navegación. Para la mejor experiencia de usuario, recomendamos diseñar estados de carga que sean significativos y ayuden a los usuarios a entender que la aplicación está respondiendo. Por ejemplo, puedes usar esqueletos y spinners, o una pequeña pero significativa parte de las pantallas futuras, como una foto de portada, título, etc.
En desarrollo, puedes previsualizar e inspeccionar el estado de carga de tus componentes usando las React Devtools.
Ejemplos
Obtención de datos secuencial
La obtención de datos secuencial ocurre cuando los componentes anidados en un árbol obtienen cada uno sus propios datos y las solicitudes no se deduplican, lo que lleva a tiempos de respuesta más largos.

Puede haber casos en los que desees este patrón porque una obtención depende del resultado de la otra.
Por ejemplo, el componente <Playlists>
solo comenzará a obtener datos una vez que el componente <Artist>
haya terminado de obtener datos porque <Playlists>
depende del prop artistID
:
export default async function Page({
params,
}: {
params: Promise<{ username: string }>
}) {
const { username } = await params
// Obtener información del artista
const artist = await getArtist(username)
return (
<>
<h1>{artist.name}</h1>
{/* Mostrar UI de respaldo mientras se carga el componente Playlists */}
<Suspense fallback={<div>Loading...</div>}>
{/* Pasar el ID del artista al componente Playlists */}
<Playlists artistID={artist.id} />
</Suspense>
</>
)
}
async function Playlists({ artistID }: { artistID: string }) {
// Usar el ID del artista para obtener listas de reproducción
const playlists = await getArtistPlaylists(artistID)
return (
<ul>
{playlists.map((playlist) => (
<li key={playlist.id}>{playlist.name}</li>
))}
</ul>
)
}
export default async function Page({ params }) {
const { username } = await params
// Obtener información del artista
const artist = await getArtist(username)
return (
<>
<h1>{artist.name}</h1>
{/* Mostrar UI de respaldo mientras se carga el componente Playlists */}
<Suspense fallback={<div>Loading...</div>}>
{/* Pasar el ID del artista al componente Playlists */}
<Playlists artistID={artist.id} />
</Suspense>
</>
)
}
async function Playlists({ artistID }) {
// Usar el ID del artista para obtener listas de reproducción
const playlists = await getArtistPlaylists(artistID)
return (
<ul>
{playlists.map((playlist) => (
<li key={playlist.id}>{playlist.name}</li>
))}
</ul>
)
}
Para mejorar la experiencia del usuario, debes usar React <Suspense>
para mostrar un fallback
mientras se obtienen los datos. Esto habilitará streaming y evitará que toda la ruta se bloquee por las solicitudes de datos secuenciales.
Obtención de datos en paralelo
La obtención de datos en paralelo ocurre cuando las solicitudes de datos en una ruta se inician de forma anticipada y comienzan al mismo tiempo.
Por defecto, los diseños y páginas se renderizan en paralelo. Por lo tanto, cada segmento comienza a obtener datos lo antes posible.
Sin embargo, dentro de cualquier componente, múltiples solicitudes async
/await
aún pueden ser secuenciales si se colocan una después de la otra. Por ejemplo, getAlbums
se bloqueará hasta que getArtist
se resuelva:
import { getArtist, getAlbums } from '@/app/lib/data'
export default async function Page({ params }) {
// Estas solicitudes serán secuenciales
const { username } = await params
const artist = await getArtist(username)
const albums = await getAlbums(username)
return <div>{artist.name}</div>
}
Puede iniciar solicitudes en paralelo definiéndolas fuera de los componentes que usan los datos y resolviéndolas juntas, por ejemplo, con Promise.all
:
import Albums from './albums'
async function getArtist(username: string) {
const res = await fetch(`https://api.example.com/artist/${username}`)
return res.json()
}
async function getAlbums(username: string) {
const res = await fetch(`https://api.example.com/artist/${username}/albums`)
return res.json()
}
export default async function Page({
params,
}: {
params: Promise<{ username: string }>
}) {
const { username } = await params
const artistData = getArtist(username)
const albumsData = getAlbums(username)
// Inicia ambas solicitudes en paralelo
const [artist, albums] = await Promise.all([artistData, albumsData])
return (
<>
<h1>{artist.name}</h1>
<Albums list={albums} />
</>
)
}
import Albums from './albums'
async function getArtist(username) {
const res = await fetch(`https://api.example.com/artist/${username}`)
return res.json()
}
async function getAlbums(username) {
const res = await fetch(`https://api.example.com/artist/${username}/albums`)
return res.json()
}
export default async function Page({ params }) {
const { username } = await params
const artistData = getArtist(username)
const albumsData = getAlbums(username)
// Inicia ambas solicitudes en paralelo
const [artist, albums] = await Promise.all([artistData, albumsData])
return (
<>
<h1>{artist.name}</h1>
<Albums list={albums} />
</>
)
}
Nota importante: Si una solicitud falla al usar
Promise.all
, toda la operación fallará. Para manejar esto, puede usar el métodoPromise.allSettled
en su lugar.
Precarga de datos
Puede precargar datos creando una función de utilidad que llame de forma anticipada antes de las solicitudes bloqueantes. <Item>
se renderiza condicionalmente basado en la función checkIsAvailable()
.
Puede llamar a preload()
antes de checkIsAvailable()
para iniciar de forma anticipada las dependencias de datos de <Item/>
. Para cuando <Item/>
se renderice, sus datos ya habrán sido obtenidos.
import { getItem } from '@/lib/data'
export default async function Page({
params,
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params
// comienza a cargar los datos del ítem
preload(id)
// realiza otra tarea asíncrona
const isAvailable = await checkIsAvailable()
return isAvailable ? <Item id={id} /> : null
}
export const preload = (id: string) => {
// void evalúa la expresión dada y retorna undefined
// https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/void
void getItem(id)
}
export async function Item({ id }: { id: string }) {
const result = await getItem(id)
// ...
}
import { getItem } from '@/lib/data'
export default async function Page({ params }) {
const { id } = await params
// comienza a cargar los datos del ítem
preload(id)
// realiza otra tarea asíncrona
const isAvailable = await checkIsAvailable()
return isAvailable ? <Item id={id} /> : null
}
export const preload = (id) => {
// void evalúa la expresión dada y retorna undefined
// https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/void
void getItem(id)
}
export async function Item({ id }) {
const result = await getItem(id)
// ...
Además, puede usar la función cache
de React y el paquete server-only
para crear una función de utilidad reutilizable. Este enfoque le permite almacenar en caché la función de obtención de datos y asegurar que solo se ejecute en el servidor.
import { cache } from 'react'
import 'server-only'
import { getItem } from '@/lib/data'
export const preload = (id: string) => {
void getItem(id)
}
export const getItem = cache(async (id: string) => {
// ...
})
import { cache } from 'react'
import 'server-only'
import { getItem } from '@/lib/data'
export const preload = (id) => {
void getItem(id)
}
export const getItem = cache(async (id) => {
// ...
})