Volver a Next.js Básico
Obtención de Datos y Server Actions
Next.js ofrece múltiples estrategias para obtener datos según cuándo y con qué frecuencia cambian.
Fetch en Componentes de Servidor
// app/productos/page.tsx
// Se ejecuta en el servidor — sin estado de carga, sin useEffect
async function obtenerProductos() {
const res = await fetch('https://api.example.com/productos', {
next: { revalidate: 3600 }, // Caché por 1 hora (ISR)
});
if (!res.ok) throw new Error('Error al obtener productos');
return res.json();
}
export default async function PaginaProductos() {
const productos = await obtenerProductos();
return (
<ul>
{productos.map((p: { id: number; nombre: string; precio: number }) => (
<li key={p.id}>
{p.nombre} — ${p.precio}
</li>
))}
</ul>
);
}
Estrategias de Caché
// Estático (cacheado para siempre, solo se regenera en deploy)
fetch(url, { cache: 'force-cache' });
// Dinámico (sin caché, siempre actualizado)
fetch(url, { cache: 'no-store' });
// ISR (revalidar cada N segundos)
fetch(url, { next: { revalidate: 60 } });
// Revalidación por etiqueta
fetch(url, { next: { tags: ['productos'] } });
Server Actions — Mutaciones
Los Server Actions son funciones async marcadas con 'use server' — se ejecutan en el servidor y pueden llamarse directamente desde componentes.
// app/actions.ts
'use server';
import { revalidateTag } from 'next/cache';
export async function crearProducto(formData: FormData) {
const nombre = formData.get('nombre') as string;
const precio = Number(formData.get('precio'));
// Validar datos
if (!nombre || precio <= 0) {
return { error: 'Datos inválidos' };
}
// Guardar en base de datos
await db.producto.create({ data: { nombre, precio } });
// Invalidar caché
revalidateTag('productos');
return { success: true };
}
// app/productos/nuevo/page.tsx
import { crearProducto } from '../actions';
export default function NuevoProductoPage() {
return (
<form action={crearProducto}>
<input name="nombre" placeholder="Nombre del producto" required />
<input name="precio" type="number" placeholder="Precio" required />
<button type="submit">Crear Producto</button>
</form>
);
}
UI Optimista con useActionState
'use client';
import { useActionState } from 'react';
import { crearProducto } from '../actions';
export function FormularioProducto() {
const [estado, formAction, pendiente] = useActionState(crearProducto, null);
return (
<form action={formAction}>
{estado?.error && <p className="text-red-500">{estado.error}</p>}
{estado?.success && <p className="text-green-500">¡Producto creado!</p>}
<input name="nombre" placeholder="Nombre del producto" required />
<input name="precio" type="number" required />
<button type="submit" disabled={pendiente}>
{pendiente ? 'Creando...' : 'Crear Producto'}
</button>
</form>
);
}
Obtención de Datos en Paralelo
export default async function PaginaDashboard() {
// Fetch en paralelo — más eficiente que awaits secuenciales
const [usuario, posts, analíticas] = await Promise.all([
obtenerUsuario(),
obtenerPosts(),
obtenerAnaliticas(),
]);
return (
<div>
<TarjetaUsuario usuario={usuario} />
<ListaPosts posts={posts} />
<GraficoAnaliticas datos={analíticas} />
</div>
);
}