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