Data Fetching y "use cache"
En NextJS 16, obtienes datos directamente en Server Components con fetch o cualquier llamada async. El cambio mas grande es "use cache", que simplifica todo el sistema de caching.
Fetch en Server Components
// app/productos/page.tsx
export default async function ProductosPage() {
const res = await fetch("https://api.example.com/productos")
const productos = await res.json()
return (
<ul>
{productos.map((p: { id: string; nombre: string }) => (
<li key={p.id}>{p.nombre}</li>
))}
</ul>
)
}
El fetch se ejecuta en el servidor. El resultado se renderiza como HTML y se envía al cliente. Cero JavaScript extra.
"use cache" — Lo nuevo de v16
v16Nuevo
La directiva "use cache" marca funciones o componentes para que Next.js cachee su resultado automaticamente. Reemplaza unstable_cache de v15 y la mayor parte de la configuración de cache en fetch.
En funciones
// lib/data.ts
export async function getProductos() {
"use cache"
const res = await fetch("https://api.example.com/productos")
return res.json()
}
La primera vez que se llama getProductos(), ejecuta el fetch. Las siguientes veces, devuelve el resultado cacheado sin hacer otro request.
En componentes completos
// components/ProductList.tsx
export default async function ProductList() {
"use cache"
const res = await fetch("https://api.example.com/productos")
const productos = await res.json()
return (
<ul>
{productos.map((p: { id: string; nombre: string }) => (
<li key={p.id}>{p.nombre}</li>
))}
</ul>
)
}
Con parametros
Cuando la funcion recibe parametros, el cache es por combinacion de argumentos:
export async function getProducto(id: string) {
"use cache"
const res = await fetch(`https://api.example.com/productos/${id}`)
return res.json()
}
// getProducto("1") y getProducto("2") se cachean por separado
Comparacion: v15 vs v16
// v15: habia que configurar cache en cada fetch
const res = await fetch("https://api.example.com/datos", {
next: { revalidate: 3600 }, // revalidar cada hora
})
// v15: o usar unstable_cache para funciones custom
import { unstable_cache } from "next/cache"
const getDatos = unstable_cache(
async () => {
return await db.query("SELECT * FROM datos")
},
["datos-cache-key"],
{ revalidate: 3600 }
)
// v16: una directiva y listo
async function getDatos() {
"use cache"
return await db.query("SELECT * FROM datos")
}
Fetch sin cache
Si necesitas datos frescos en cada request (SSR puro):
export default async function Page() {
const res = await fetch("https://api.example.com/tiempo-real", {
cache: "no-store",
})
const data = await res.json()
return <div>Temperatura: {data.temp}</div>
}
Acceso directo a base de datos
No necesitas crear una API para acceder a tu DB desde un Server Component:
// app/usuarios/page.tsx
import { db } from "@/lib/database"
export default async function UsuariosPage() {
const usuarios = await db.usuario.findMany({
select: { id: true, nombre: true, email: true },
orderBy: { createdAt: "desc" },
take: 20,
})
return (
<table>
<thead>
<tr>
<th>Nombre</th>
<th>Email</th>
</tr>
</thead>
<tbody>
{usuarios.map((u) => (
<tr key={u.id}>
<td>{u.nombre}</td>
<td>{u.email}</td>
</tr>
))}
</tbody>
</table>
)
}
Ejemplo real: API con cache
Imagina que tienes un dashboard que muestra metricas. Las metricas no cambian cada segundo, asi que tiene sentido cachearlas:
// lib/metricas.ts
export async function getMetricas() {
"use cache"
const [ventas, usuarios, pedidos] = await Promise.all([
db.venta.count({ where: { createdAt: { gte: inicioMes() } } }),
db.usuario.count(),
db.pedido.count({ where: { status: "pendiente" } }),
])
return { ventas, usuarios, pedidos }
}
// app/dashboard/page.tsx
import { getMetricas } from "@/lib/metricas"
export default async function DashboardPage() {
const { ventas, usuarios, pedidos } = await getMetricas()
return (
<div className="grid grid-cols-3 gap-4">
<Card titulo="Ventas del mes" valor={ventas} />
<Card titulo="Usuarios totales" valor={usuarios} />
<Card titulo="Pedidos pendientes" valor={pedidos} />
</div>
)
}
function Card({ titulo, valor }: { titulo: string; valor: number }) {
return (
<div className="bg-gray-800 rounded p-6">
<p className="text-sm text-gray-400">{titulo}</p>
<p className="text-3xl font-bold">{valor}</p>
</div>
)
}
Las metricas se calculan una vez y se cachean. Se refrescan cuando revalidas el cache (ver siguiente seccion: Caching Avanzado).