TechLead

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 fresco
next: { revalidate: N } - Revalidar cada N segundos
next: { tags: [...] } - Revalidación basada en tags

Obtenció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>
  );
}

📖 Documentación de Obtención de Datos →

✅ 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