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

tsx
// 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

tsx
// 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

tsx
// 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:

tsx
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

tsx
// 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):

tsx
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:

tsx
// 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:

tsx
// 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 }
}
tsx
// 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).