Obtención de Datos
Aprende obtención de datos del lado del servidor, cacheo y estrategias de revalidación
Obtención de Datos en Server Components
Next.js extiende la API nativa fetch con opciones de cacheo y revalidación.
En Server Components, puedes obtener datos directamente usando async/await sin useEffect o
librerías externas.
Las peticiones se ejecutan en el servidor por defecto, así que puedes mantener credenciales privadas y enviar solo el HTML renderizado al navegador.
🗄️ Opciones de Cacheo
force-cache - Cachear indefinidamente (por defecto para estático)no-store - Nunca cachear, siempre fresconext: { revalidate: N } - Revalidar cada N segundosnext: { tags: [...] } - Revalidación basada en tagsObtención Básica de Datos
Este ejemplo muestra un helper del lado del servidor que obtiene posts, aplica opciones de cacheo y lanza un error para peticiones fallidas. La página espera los datos y los renderiza directamente.
// app/posts/page.tsx - Componente Servidor
async function getPosts() {
const res = await fetch('https://api.example.com/posts', {
// Opciones de cacheo
cache: 'force-cache', // Por defecto - cachear para siempre
// O
cache: 'no-store', // Siempre obtener datos frescos
});
if (!res.ok) throw new Error('Falló la obtención de posts');
return res.json();
}
export default async function PostsPage() {
const posts = await getPosts();
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
Revalidación Basada en Tiempo (ISR)
Usa revalidación para contenido que cambia en un horario. Puedes configurarlo por petición o para toda la página, y controlar renderizado estático versus dinámico.
// Revalidar datos cada 60 segundos
async function getProducts() {
const res = await fetch('https://api.example.com/products', {
next: { revalidate: 60 }, // Segundos
});
return res.json();
}
// Revalidación a nivel de página - afecta todas las obtenciones
export const revalidate = 60; // Revalidar página cada 60 segundos
// Comportamiento dinámico
export const dynamic = 'force-dynamic'; // Siempre dinámico
export const dynamic = 'force-static'; // Siempre estático
export const dynamic = 'auto'; // Por defecto - auto detectar
Revalidación Bajo Demanda
Los tags de caché te permiten revalidar solo los datos que cambiaron. Activa la invalidación desde un Server Action o un Route Handler impulsado por webhook.
// Revalidación basada en tags
async function getPost(slug: string) {
const res = await fetch(`https://api.example.com/posts/${slug}`, {
next: { tags: ['posts', `post-${slug}`] },
});
return res.json();
}
// Revalidar vía Server Action o Route Handler
// app/actions.ts
'use server';
import { revalidateTag, revalidatePath } from 'next/cache';
export async function updatePost(slug: string) {
// Actualizar en base de datos...
// Revalidar tag específico
revalidateTag(`post-${slug}`);
// O revalidar todos los posts
revalidateTag('posts');
// O revalidar una ruta específica
revalidatePath('/posts');
revalidatePath(`/posts/${slug}`);
}
// Route Handler para webhooks
// app/api/revalidate/route.ts
import { revalidateTag } from 'next/cache';
import { NextRequest } from 'next/server';
export async function POST(request: NextRequest) {
const { tag, secret } = await request.json();
if (secret !== process.env.REVALIDATION_SECRET) {
return Response.json({ error: 'Secreto inválido' }, { status: 401 });
}
revalidateTag(tag);
return Response.json({ revalidated: true });
}
Obtención de Datos en Paralelo
La obtención en paralelo evita cascadas. El tiempo total de espera se convierte en la petición más lenta en lugar de la suma de todas las peticiones.
// ✅ BIEN: Obtener en paralelo
async function getData() {
// Iniciar todas las obtenciones al mismo tiempo
const [users, posts, comments] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/comments').then(r => r.json()),
]);
return { users, posts, comments };
}
// ❌ MAL: Obtención secuencial (cascada)
async function getDataSlow() {
const users = await fetch('/api/users').then(r => r.json());
const posts = await fetch('/api/posts').then(r => r.json());
const comments = await fetch('/api/comments').then(r => r.json());
// ¡Cada uno espera al anterior!
}
Patrones de Obtención de Datos
Obtén cerca de donde se renderizan los datos para mantener componentes independientes y beneficiarte de la deduplicación automática de peticiones. El patrón preload inicia el trabajo temprano para prevenir cascadas.
// Patrón 1: Obtener a nivel de componente (recomendado)
// Cada componente obtiene sus propios datos
async function UserProfile({ userId }: { userId: string }) {
const user = await getUser(userId);
return <div>{user.name}</div>;
}
async function UserPosts({ userId }: { userId: string }) {
const posts = await getUserPosts(userId);
return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>;
}
// ¡Next.js deduplica automáticamente peticiones idénticas!
// Si ambos componentes obtienen el mismo usuario, solo se hace 1 petición
// Patrón 2: Patrón preload para cascadas
import { preload } from './data';
export default async function Page({ params }: { params: { id: string } }) {
// Iniciar obtención temprano
preload(params.id);
// Hacer otro trabajo...
// Los datos ya se están obteniendo
const data = await getData(params.id);
}
Usar Bases de Datos Directamente
Los Server Components pueden consultar bases de datos directamente sin una capa API. Mantén credenciales en el servidor y retorna solo los datos que necesitas para renderizar.
// En Server Components, acceder a bases de datos directamente
// ¡No se necesitan rutas API!
import { prisma } from '@/lib/prisma';
import { sql } from '@vercel/postgres';
// Usando Prisma
async function getUsers() {
const users = await prisma.user.findMany({
where: { active: true },
include: { posts: true },
});
return users;
}
// Usando SQL crudo
async function getProducts() {
const { rows } = await sql`
SELECT * FROM products
WHERE stock > 0
ORDER BY created_at DESC
`;
return rows;
}
// Usando Drizzle
import { db } from '@/lib/drizzle';
import { users } from '@/lib/schema';
async function getAllUsers() {
return db.select().from(users);
}
Estados de Carga con Suspense
Suspense transmite la página en fragmentos. El contenido rápido se renderiza inmediatamente mientras las secciones más lentas muestran sus propios estados de carga hasta que llegan los datos.
import { Suspense } from 'react';
// Componente lento que obtiene datos
async function SlowComponent() {
const data = await fetchSlowData(); // Toma 3 segundos
return <div>{data}</div>;
}
// Componente rápido
async function FastComponent() {
const data = await fetchFastData(); // Toma 100ms
return <div>{data}</div>;
}
// Página con streaming
export default function Page() {
return (
<div>
{/* Se muestra inmediatamente */}
<h1>Dashboard</h1>
{/* Componente rápido carga primero */}
<Suspense fallback={<p>Cargando rápido...</p>}>
<FastComponent />
</Suspense>
{/* Componente lento se transmite después */}
<Suspense fallback={<p>Cargando lento...</p>}>
<SlowComponent />
</Suspense>
</div>
);
}
✅ Mejores Prácticas de Obtención de Datos
- • Obtener datos a nivel de componente, no en la parte superior
- • Usar obtención paralela con Promise.all()
- • Aprovechar la deduplicación automática de peticiones
- • Usar Suspense para estados de carga
- • Elegir estrategia de cacheo apropiada por tipo de datos
- • Usar tags para control granular de revalidación