Un Fetching Data es una solicitud HTTP para obtener datos de un servidor. Puedes usarlo en Next.js para obtener datos en Server Components y Client Components.
Con App Router puedes obtener datos desde Server Components o Client Components.
En Server Components normalmente usas fetch (o consultas a DB/ORM). En Client Components usas useEffect, librerías como SWR o React Query, o el hook use para streaming.
Antes de continuar, recuerda que un Server Component se renderiza en el servidor y se envía al cliente como HTML estático. Por lo tanto, no puedes usar hooks de React en un Server Component. A continuación, veremos un ejemplo mínimo de cómo obtener datos en un Server Component.
Archivo: app/blog/page.jsx
// app/blog/page.jsx
export default async function Page() {
const res = await fetch('https://devsapihub.com/api-fast-food')
const foods = await res.json()
return (
<ul>
{foods.map(f => <li key={f.id}>{f.name}</li>)}
</ul>
)
} async y await el fetch.fetch en Server Components no se cachea por defecto en la respuesta del fetch, aunque Next.js pre-renderiza la ruta para mejorar el rendimiento. Si quieres evitar el pre-render (renderizado dinámico), usa la opción { cache: 'no-store' } en el fetch.fetch (lo esencial)await fetch(url, { cache: 'no-store' }) Next.js también tiene mecanismos para deduplicar fetch automáticamente en un mismo render (request memoization).
El Server Component devuelve una promesa pendiente, que luego el Client Component resuelve usando use() dentro de un <Suspense>.
Esto permite mostrar contenido parcial mientras los datos se cargan.
// app/blog/page.jsx (Server Component)
import Posts from '@/app/ui/posts'
import { Suspense } from 'react'
// función simulada para obtener posts
async function getPosts() {
const res = await fetch('https://jsonplaceholder.typicode.com/posts')
return res.json()
}
export default async function Page() {
const postsPromise = getPosts() // 👈 no hacemos await aquí
return (
<Suspense fallback={<div>Cargando...</div>}>
<Posts posts={postsPromise} />
</Suspense>
)
} // app/ui/posts.jsx (Client Component)
'use client'
import { use } from 'react'
export default function Posts({ posts }) {
const allPosts = use(posts) // 👈 resuelve la promesa aquí
return (
<ul>
{allPosts.map(p => (
<li key={p.id}>{p.title}</li>
))}
</ul>
)
} getPosts() debe ser async, ya que devuelve una promesa.Page, no hagas await a esa promesa; déjala pendiente para que use() la resuelva en el cliente.use() solo puede usarse dentro de un Client Component, como en el ejemplo.Para obtener datos en un Client Component, puedes usar useEffect con fetch, o librerías como SWR o React Query.
useEffect + fetch (clásico)'use client' // Directiva para indicar que es un Client Component
import { useEffect, useState } from 'react' // Importamos useState para manejar el estado local y useEffect para obtener datos
export default function BlogClient() {
const [posts, setPosts] = useState([]) // Estado local para almacenar los posts obtenidos
// useEffect se ejecuta solo en el cliente (no en el servidor)
// Ideal para obtener datos dinámicos o revalidarlos después del render inicial
useEffect(() => {
fetch('/api/posts')
.then(r => r.json())
.then(setPosts) // actualiza el estado con los datos obtenidos
}, [])
// Render simple de la lista de posts
return (
<ul>
{posts.map(p => <li key={p.id}>{p.title}</li>)}
</ul>
)
} En este caso, useEffect se ejecuta cuando el componente se monta, y obtiene los posts de la API.
Veamos este caso, donde useSWR obtiene los posts de la API y los almacena en el estado local.
Luego, renderiza la lista de posts o un mensaje de error si ocurre algún problema.
'use client'
import useSWR from 'swr'
// Definimos un "fetcher": función encargada de obtener y parsear los datos
const fetcher = url => fetch(url).then(r => r.json())
export default function BlogSWR() {
// useSWR maneja automáticamente cache, revalidación y estados de carga/error
const { data, error, isLoading } = useSWR('/api/posts', fetcher)
// Mostrar mensaje mientras se cargan los datos
if (isLoading) return <div>Cargando...</div>
// Mostrar mensaje si ocurre un error en la petición
if (error) return <div>Error al cargar los datos</div>
// Renderizar los posts cuando la data está disponible
return (
<ul>
{data.map(p => <li key={p.id}>{p.title}</li>)}
</ul>
)
} cache: 'no-store' si necesitas datos siempre frescos (evita el pre-render).fetch y el streaming con use + <Suspense> para UX más fluida.