Caching Avanzado

Una vez que entiendes "use cache" (ver Data Fetching), el siguiente paso es controlar cuando y como se actualiza ese cache.

Revalidacion por tiempo

Usa cacheLife para definir cuanto tiempo dura el cache:

tsx
import { cacheLife } from "next/cache"

async function getProductos() {
  "use cache"
  cacheLife("hours") // Cache por 1 hora

  const res = await fetch("https://api.example.com/productos")
  return res.json()
}

Perfiles disponibles:

PerfilDuracion
"seconds"1 segundo
"minutes"5 minutos
"hours"1 hora
"days"1 dia
"weeks"1 semana
"max"El maximo posible

Revalidacion on-demand

Para invalidar el cache cuando algo cambia (despues de una mutation):

revalidatePath

Revalida todas las funciones con "use cache" que se usan en esa ruta:

tsx
"use server"

import { revalidatePath } from "next/cache"

export async function actualizarProducto(id: string, data: ProductoData) {
  await db.producto.update({ where: { id }, data })

  // Revalida la pagina de productos
  revalidatePath("/productos")

  // Revalida la pagina especifica del producto
  revalidatePath(`/productos/${id}`)
}

revalidateTag

Mas preciso. Revalida solo las funciones marcadas con un tag especifico:

tsx
import { cacheTag } from "next/cache"

async function getProductos() {
  "use cache"
  cacheTag("productos")

  return await db.producto.findMany()
}

async function getProducto(id: string) {
  "use cache"
  cacheTag("productos", `producto-${id}`)

  return await db.producto.findUnique({ where: { id } })
}
tsx
"use server"

import { revalidateTag } from "next/cache"

export async function actualizarProducto(id: string, data: ProductoData) {
  await db.producto.update({ where: { id }, data })

  // Solo revalida lo que tenga el tag "productos"
  revalidateTag("productos")

  // O revalida solo un producto especifico
  revalidateTag(`producto-${id}`)
}
revalidatePath vs revalidateTag

Usa revalidatePath cuando quieres refrescar toda una pagina. Usa revalidateTag cuando quieres ser mas preciso sobre que datos refrescar.

Comparacion con v15

v16Cambio
Conceptov15v16
Cache manualunstable_cache()"use cache"
Cache en fetchnext: { revalidate: 60 }"use cache" + cacheLife()
Tags de cachenext: { tags: ["key"] }cacheTag("key")
RevalidacionrevalidateTag() / revalidatePath()Igual (no cambio)

Estrategias comunes

Datos que cambian poco (catalogo, CMS)

tsx
async function getArticulos() {
  "use cache"
  cacheLife("hours")
  cacheTag("articulos")

  return await db.articulo.findMany({
    where: { publicado: true },
    orderBy: { fecha: "desc" },
  })
}

Se revalida automaticamente cada hora, o manualmente cuando publicas un articulo.

Datos que cambian seguido (notificaciones, chat)

No uses cache. Usa fetch con cache: "no-store" o simplemente no pongas "use cache":

tsx
async function getNotificaciones(userId: string) {
  // Sin "use cache" = datos frescos en cada request
  return await db.notificacion.findMany({
    where: { userId, leida: false },
    orderBy: { createdAt: "desc" },
  })
}

ISR (Incremental Static Regeneration)

Para paginas estaticas que se actualizan periodicamente:

tsx
// app/blog/[slug]/page.tsx
export default async function BlogPost({
  params,
}: {
  params: Promise<{ slug: string }>
}) {
  "use cache"
  cacheLife("days")

  const { slug } = await params
  const post = await getPost(slug)

  return <article>{/* ... */}</article>
}

export async function generateStaticParams() {
  const posts = await getAllPosts()
  return posts.map((p) => ({ slug: p.slug }))
}

La pagina se genera en build time, se sirve estaticamente, y se regenera cada dia.

Ejemplo: e-commerce con cache inteligente

tsx
// lib/data.ts
import { cacheTag, cacheLife } from "next/cache"

// Catalogo: cambia poco, cache largo
export async function getCategorias() {
  "use cache"
  cacheLife("days")
  cacheTag("categorias")

  return await db.categoria.findMany({ include: { _count: true } })
}

// Productos: cache medio, revalidar al editar
export async function getProductos(categoriaId?: string) {
  "use cache"
  cacheLife("hours")
  cacheTag("productos")

  return await db.producto.findMany({
    where: categoriaId ? { categoriaId } : undefined,
    include: { categoria: true },
  })
}

// Precio y stock: siempre frescos (sin cache)
export async function getStock(productoId: string) {
  return await db.inventario.findUnique({
    where: { productoId },
    select: { cantidad: true, precio: true },
  })
}
tsx
// app/actions/admin.ts
"use server"

import { revalidateTag } from "next/cache"

export async function editarProducto(id: string, data: ProductoData) {
  await db.producto.update({ where: { id }, data })
  revalidateTag("productos")
}

export async function editarCategoria(id: string, data: CategoriaData) {
  await db.categoria.update({ where: { id }, data })
  revalidateTag("categorias")
  revalidateTag("productos") // Los productos muestran la categoria
}