Importante: Este artículo se ha actualizado
Cuando lo escribí por primera vez, use cache estaba en la versión canary de Next.js 15 y decía "no lo uses en
producción". Ya no es así: con Next.js 16.0 pasó a la versión estable como parte
de la función Cache Components. He reescrito el artículo desde cero según la
documentación de 16.x: aquí tienes tanto el uso correcto como los puntos donde más
gente se atasca.
Durante mucho tiempo, el caché del lado del servidor en Next.js fue un tema disperso: un comportamiento propio para las llamadas a fetch, una API aparte para unstable_cache y React.cache para las repeticiones dentro de una misma solicitud. use cache sustituye a los tres con una única directiva: cacheas la salida de una función, de un componente o de un archivo entero, y Next.js genera la clave por sí mismo.
Primero: sin activarlo, no funciona
Este es el paso que más se pasa por alto. Escribir use cache no basta; tienes que activar la función Cache Components:
Ejemplo: next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
cacheComponents: true,
};
export default nextConfig;Si escribes la directiva sin activarlo, no se cachea nada y todo sigue funcionando en silencio, sin que veas ningún error. Es lo que más tiempo te hace perder cuando buscas el motivo de la lentitud.
¿Dónde se escribe?
Hay tres lugares y en los tres la función tiene que ser async:
Ejemplo: Nivel de función, de componente y de archivo
// 1. Fonksiyon seviyesi
export async function getData() {
"use cache";
const res = await fetch("https://api.example.com/data");
return res.json();
}
// 2. Bileşen seviyesi — prop'lar önbellek anahtarına girer
export async function Bookings({ type = "haircut" }: { type: string }) {
"use cache";
const res = await fetch(`https://api.example.com/bookings?type=${type}`);
return <List items={await res.json()} />;
}// 3. Dosya seviyesi — dosyadaki her export önbelleğe alınır
"use cache";
export async function getMonthlyTotals(accountId: string) {
return db.orders.aggregate({ where: { accountId }, _sum: { amount: true } });
}
export async function getTopProducts() {
return db.products.findMany({ orderBy: { sales: "desc" }, take: 10 });
}Si lo usas a nivel de archivo, todos los exports quedan incluidos, también generateMetadata y generateStaticParams; todos tienen que ser async. Si quieres generar por adelantado un segmento de ruta por completo, debes poner la directiva al principio de cada archivo que forme esa ruta (page, layout, slots de rutas paralelas), uno por uno: cada segmento es una entrada de caché independiente.
¿Cómo se genera la clave de caché?
La clave se compone de cuatro partes:
- Build ID: cambia en cada compilación, es decir, cada despliegue invalida toda la caché
- Identidad de la función: la ubicación de la función en el código y un resumen seguro de su firma
- Argumentos serializables: las props en un componente, los parámetros en una función
- Resumen de HMR: solo en desarrollo, descarta la caché con el hot reload
El matiz importante no está en el cuarto punto, sino en los argumentos: las variables que usas del ámbito exterior también se cuentan automáticamente como argumentos y entran en la clave.
Importante: El cierre (closure) forma parte de la clave
async function Component({ userId }: { userId: string }) {
const getData = async (filter: string) => {
"use cache";
// Anahtar hem userId'yi (kapanıştan) hem filter'ı (argümandan) içerir
const res = await fetch(
`https://api.example.com/users/${userId}/data?filter=${filter}`
);
return res.json();
};
return getData("active");
}La buena noticia: no vas a mostrar por error los datos de un usuario a otro. La mala: sin darte cuenta puedes diversificar la clave y reducir la tasa de aciertos de la caché. Recuerda que cada variable externa que uses dentro divide la clave.
Cuánto tiempo se mantendrá fresco: cacheLife
Si no llamas a cacheLife, se aplica el perfil default:
- stale: 5 minutos (lado del cliente)
- revalidate: 15 minutos (lado del servidor)
- expire: nunca expira con el tiempo
Consejo: Escribe la duración de forma explícita
import { cacheLife } from "next/cache";
export async function getOrderSummary(accountId: string) {
"use cache";
cacheLife("hours");
const orders = await getOrders(accountId);
return { orders };
}La recomendación de la documentación es clara: escribe cacheLife de forma explícita en cada ámbito de use cache. Si no, el comportamiento no se ve en el punto de la llamada y depende del valor por defecto o de la caché que lo envuelve.
Una advertencia: si colocas otro use cache de corta duración dentro de un use cache que no tiene cacheLife, la compilación da error. En cachés anidadas tienes que elegir las duraciones de forma deliberada.
Etiqueta y limpia a mano: cacheTag
La actualización basada en el tiempo no siempre es la respuesta correcta. Si quieres limpiar la caché al instante cuando cambia el contenido, usa etiquetas:
Ejemplo: cacheTag + updateTag
// lib/products.ts
import { cacheTag } from "next/cache";
export async function getProducts() {
"use cache";
cacheTag("products");
const res = await fetch("https://api.example.com/products");
return res.json();
}// app/actions.ts
"use server";
import { updateTag } from "next/cache";
export async function updateProduct() {
await db.products.update(/* ... */);
updateTag("products"); // "products" etiketli tüm girdiler düşer
}En la práctica se usan juntos: un artículo de blog que cambia rara vez recibe un cacheLife largo más cacheTag y se limpia solo cuando se guarda; una lista que fluye a lo largo del día se refresca sola con un perfil corto.
Donde más gente se atasca: cookies() no funciona dentro
Dentro de un ámbito cacheado no se pueden usar cookies(), headers() ni searchParams. Además, la restricción sigue la pila de llamadas: si una función auxiliar que llamas desde dentro lee alguno de ellos, también falla (next-request-in-use-cache).
Precaución: La parte traicionera
En una ruta que se renderiza de forma dinámica, este error aparece en tiempo de ejecución, no en la compilación. Es decir, next build puede pasar sin problemas y fallar con next start. Verde en CI, rojo en producción.
El patrón correcto: lee los datos de la solicitud fuera del ámbito de la caché y pásalos dentro como argumento.
Explicación: Incorrecto / correcto
// YANLIŞ — çalışma anında patlar
async function getCart() {
"use cache";
const store = await cookies();
return db.cart.find({ id: store.get("cart")?.value });
}
// DOĞRU — okuma dışarıda, argüman içeride
export default async function Page() {
const store = await cookies();
const cartId = store.get("cart")?.value;
return <Cart cartId={cartId} />;
}
async function Cart({ cartId }: { cartId?: string }) {
"use cache";
return <CartView data={await db.cart.find({ id: cartId })} />;
}Para los casos en que realmente es inevitable existe use cache: private, pero la documentación lo marca como "muy raramente": para requisitos de cumplimiento normativo o situaciones en las que no puedes refactorizar el código.
Límites de serialización
Los argumentos y los valores devueltos tienen que ser serializables.
Válidos: tipos primitivos, objetos planos, arrays, Date, Map, Set, TypedArray, ArrayBuffer. El valor devuelto también puede ser JSX.
No válidos: instancias de clases, funciones, Symbol, WeakMap/WeakSet y, algo que se olvida fácilmente, instancias de URL.
El detalle salvador aquí: puedes pasar un valor y usarlo sin mirar dentro de él. Así funcionan children y las Server Actions.
Consejo: El patrón de paso (pass-through)
async function CachedWrapper({ children }: { children: ReactNode }) {
"use cache";
// children okunmuyor, sadece geçiriliyor — anahtarı etkilemiyor
return (
<div className="wrapper">
<header>Önbelleğe alınmış başlık</header>
{children}
</div>
);
}
export default function Page() {
return (
<CachedWrapper>
<DynamicComponent /> {/* önbelleğe alınmıyor */}
</CachedWrapper>
);
}Así puedes cachear el shell estático de la página e insertar dentro la parte dinámica. Poner use cache en un layout no cachea los children que hay debajo.
YouTube
También hay un vídeo explicativo de este tema.
Ir al canal →
¿Dónde se guarda? La forma de alojamiento cambia el resultado
El manejador por defecto es una LRU en memoria. Esto se comporta de forma muy distinta según dónde publiques:
| Entorno | Comportamiento en tiempo de ejecución |
|---|---|
| Serverless | Las entradas normalmente no persisten entre solicitudes; cada solicitud puede caer en una instancia distinta. El cacheo en tiempo de compilación funciona con normalidad. |
| Tu propio servidor | Las entradas persisten entre solicitudes. El tamaño se ajusta con cacheMaxMemorySize. |
Advertencia: Una deducción errónea frecuente
La mayoría de las quejas del tipo "puse use cache pero la carga de la base de datos no bajó" vienen de esta tabla. En serverless la caché en memoria no es persistente. Si de verdad quieres una caché compartida, tienes que conectar un manejador aparte (Redis, KV) con use cache: remote, y eso también tiene latencia de red y coste de plataforma.
Además, ninguna directiva de caché se traslada a un nuevo despliegue, porque la clave incluye el build ID. Para los datos que deben persistir entre despliegues sigues necesitando unstable_cache o la caché de fetch. use cache no lo sustituye todo.
Si la compilación se atasca a los 50 segundos
Si ves este error, la causa casi siempre es la misma:
Filling a cache during prerender timed out, likely because request-specific arguments such as params, searchParams, cookies() or uncached data were used inside "use cache".
El ámbito cacheado está esperando una Promise que no se puede resolver durante la compilación. Las tres vías típicas: pasar una Promise así como prop, acceder a ella desde un closure o obtenerla a través de un Map compartido. La solución es la misma: haz el await del valor fuera y pasa datos planos al interior.
En cambio, llamar a cookies() directamente dentro no da un timeout sino un error inmediato: no los confundas.
Otros dos comportamientos que debes conocer
Con el modo borrador (Draft Mode) activado, todas las funciones cacheadas se vuelven a ejecutar en cada solicitud y el resultado no se guarda. No hay riesgo de ver contenido obsoleto en la vista previa; no necesitas escribir código adicional para ello.
React.cache está aislado. Un valor que guardes con React.cache fuera del límite de use cache no es visible desde dentro. La única forma de meter datos es mediante un argumento.
¿Cuándo no usarlo?
- No es compatible con la exportación estática (
output: "export"). Se admiten el servidor Node.js y Docker. - En datos específicos del usuario que cambian en cada solicitud: tendrías que sacarlos del ámbito y pasarlos como argumento, lo cual la mayoría de las veces vuelve inútil la caché.
- En cálculos costosos que deben sobrevivir entre despliegues: la clave con el build ID los reinicia.
- Si la frescura de los datos se mide en segundos: el enrutador del cliente impone un tiempo de obsolescencia de al menos 30 segundos, independientemente de la configuración.
Depuración
Consejo: Registro detallado
NEXT_PRIVATE_DEBUG_CACHE=1 npm run dev
# ya da üretim için
NEXT_PRIVATE_DEBUG_CACHE=1 npm run startEn desarrollo, las salidas de console.log de las funciones cacheadas aparecen con el prefijo Cache: la forma más rápida de saber qué llamada se devolvió desde la caché.
Resumen
Resumen: En pocas palabras
use cachees estable en Next.js 16.0; se activa concacheComponents: true- Funciona a nivel de función, componente, archivo y segmento de ruta; todos deben ser async
- La clave: build ID + identidad de la función + argumentos + variables capturadas del closure
- La duración con
cacheLife, la limpieza manual concacheTag+updateTag cookies(),headers(),searchParamsestán prohibidos dentro: léelos fuera y pásalos como argumento- El almacenamiento por defecto es LRU en memoria; en serverless no persiste entre solicitudes
- Si necesitas persistencia entre despliegues, sigue siendo
unstable_cacheo la caché defetch
La primera versión de este artículo tenía una sección de "prueba de rendimiento" con cifras como una mejora del 78 %. La he quitado porque esas cifras no provenían de una medición real, sino de un montaje inventado con setTimeout, y no te servían para estimar la ganancia en tu proyecto. La ganancia depende por completo de lo que cachees: cachear una consulta a la base de datos que tarda 200 ms no es lo mismo que cachear un cálculo que tarda 2 ms. Haz tu propia medición con NEXT_PRIVATE_DEBUG_CACHE=1.
Nota: Fuente
Este artículo se basa en la documentación oficial de Next.js 16.x (directiva use
cache). Historial de versiones: v15.0.0 la introdujo de forma experimental, v16.0.0 la hizo estable con Cache
Components.
Hoja de ruta y herramienta interactivas
Desarrollador Frontend Junior 2026
Lo que realmente necesitas saber para conseguir un trabajo de frontend junior en 2026 y lo que puede esperar, a partir de ofertas reales.
¿SQL o NoSQL?
Responde seis preguntas y te diré qué tipo de base de datos encaja con tu proyecto, y por qué.