WebLa caché de Next.js 16: use cache, cacheLife y updateTag
En Next.js 16 nada se cachea hasta que escribes 'use cache'. Cómo encajan los siete perfiles de cacheLife, cacheTag y updateTag, y qué cambia para fetch.

El modelo de caché de Next.js 16 es un sistema opt-in: nada entra en caché hasta que marcas una función, un componente o un archivo con la directiva 'use cache', y cada entrada lleva una duración fijada con cacheLife y, si hace falta, etiquetas que invalidas después de una escritura. Sustituye las cachés implícitas de las primeras versiones del App Router por una directiva y unas pocas funciones. Se activa con un solo flag, cacheComponents: true, y ese mismo flag convierte el Partial Prerendering en la forma por defecto de renderizar una ruta.
Le interesa a cualquier equipo que trabaje con el App Router y se haya preguntado por qué una página mostraba datos viejos, o por qué una página pensada como estática se renderizaba en cada petición. Las dos preguntas tenían la misma causa: el framework decidía qué cachear, y esa decisión costaba verla. En Next.js 16 la decisión vive en tu código, junto a los datos a los que se aplica. Aquí describimos el modelo tal como lo documenta Next.js 16.4, publicado el 6 de octubre de 2026.
La versión en treinta segundos
- Dinámico por defecto. Con Cache Components activo, cada fetch y cada consulta a la base de datos se ejecuta en el momento de la petición, salvo que esté dentro de un scope
'use cache'. - Una duración por entrada.
cacheLife('hours'), o un perfil propio, fija tres tiempos: cuánto reutiliza el navegador la entrada, cuándo la regenera el servidor en segundo plano y cuándo caduca. - Invalidación por etiqueta.
cacheTag('posts')etiqueta una entrada. Tras una escritura, conupdateTag('posts')la siguiente lectura espera a los datos frescos; conrevalidateTag('posts', 'max')se sirve la copia vieja mientras se prepara una nueva. - La shell estática sale de la caché. La salida cacheada con una duración suficiente entra en el prerender de la ruta. Las entradas de vida corta se convierten en huecos que se rellenan en el momento de la petición.
¿Por qué Next.js volvió a cambiar su modelo de caché?
Porque los valores por defecto sorprendían. En Next.js 14, fetch usaba force-cache salvo que se indicara otra cosa, los route handlers GET se cacheaban y el router del cliente guardaba en memoria los segmentos de página entre navegaciones. Una página podía servir datos viejos sin que ninguna línea de código lo dijera. Next.js 15 dio la vuelta a esos valores: los fetch, los handlers GET y las navegaciones del cliente dejaron de cachearse salvo que lo pidieras.
La sorpresa desapareció, pero las herramientas quedaron dispersas: la opción cache de fetch, next.revalidate, las configuraciones de segmento dynamic, revalidate y fetchCache, y unstable_cache para todo lo que no era un fetch. Next.js 16, publicado en octubre de 2025, las reunió en una sola directiva. Con Cache Components activo, la guía oficial de migración pide quitar dynamic, revalidate y fetchCache de las rutas y llevar la decisión de caché a los propios datos.
¿Cómo funciona 'use cache'?
'use cache' marca como cacheable una función async, un componente async o un archivo entero. La primera llamada con unos inputs concretos ejecuta el cuerpo y guarda el resultado. Las llamadas siguientes con los mismos inputs lo reutilizan, dentro del mismo render y entre peticiones, hasta que la entrada caduca.
import { cacheLife, cacheTag } from 'next/cache'
export async function getProject(id: string) {
'use cache'
cacheLife('hours')
cacheTag('projects', `project-${id}`)
return db.project.findUnique({ where: { id } })
}La clave de caché se forma con cuatro elementos: el ID de la build, un hash de la ubicación y la firma de la función, los argumentos serializados y las variables que la función captura del scope exterior. De ahí salen dos consecuencias. Los argumentos y los valores de retorno tienen que ser serializables: nada de instancias de clase ni de conexiones abiertas. Y cada deploy nuevo arranca con la caché vacía, porque el ID de la build forma parte de todas las claves.
Un scope cacheado no puede leer cookies(), headers() ni searchParams, y la restricción sigue la pila de llamadas: un helper que lee una cookie también falla si lo llamas desde una función cacheada. La documentación avisa de que en una ruta dinámica este error puede pasar next build sin problemas y aparecer solo con next start. Lo que funciona es leer los datos de la petición fuera y pasarlos como argumento; así, además, entran en la clave.
import { cookies } from 'next/headers'
export default async function Page() {
const teamId = (await cookies()).get('team')?.value ?? ''
const projects = await getProjects(teamId) // cached per teamId
return <ProjectList projects={projects} />
}¿Cuánto dura una entrada en caché?
cacheLife fija tres tiempos. stale es cuánto reutiliza el navegador la entrada sin preguntar al servidor. revalidate marca cuándo la siguiente petición lanza una regeneración en segundo plano, aunque sigue recibiendo la copia cacheada. expire marca cuándo se descarta una entrada que nadie ha pedido, y la siguiente petición espera a la salida fresca. Next.js trae siete perfiles predefinidos:
default: stale 5 minutos, revalidate 15 minutos, sin caducidad. Se aplica cuando un scope no llama acacheLife.seconds: stale 30 segundos, revalidate 1 segundo, expire 1 minuto.minutes: stale 5 minutos, revalidate 1 minuto, expire 1 hora.hours: stale 5 minutos, revalidate 1 hora, expire 1 día.days: stale 5 minutos, revalidate 1 día, expire 1 semana.weeks: stale 5 minutos, revalidate 1 semana, expire 30 días.max: stale 5 minutos, revalidate 30 días, expire 1 año.
Cualquier perfil se puede redefinir, y se pueden añadir perfiles con nombre propio en next.config.ts. La documentación recomienda llamar a cacheLife en cada scope cacheado, y estamos de acuerdo: un default implícito es lo primero que nadie recuerda seis meses después.
La duración también decide adónde puede ir la salida. Una entrada con revalidate a 0 o expire por debajo de 5 minutos se queda fuera del prerender y se convierte en un hueco dinámico. También se queda fuera un stale por debajo de 30 segundos, porque el prefetch caducaría antes del clic. De los perfiles predefinidos, solo seconds cruza esos umbrales. En el cliente, el router conserva cualquier entrada al menos 30 segundos, configures lo que configures.
¿Cómo se invalida la caché tras una escritura?
La caducidad por tiempo sirve para el contenido que cambia poco a poco. Los datos que un usuario acaba de modificar necesitan una invalidación bajo demanda, y Next.js 16 ofrece tres, cada una con una garantía distinta:
updateTag(tag)hace caducar de inmediato todas las entradas con esa etiqueta, y la siguiente lectura espera a los datos frescos. Solo funciona dentro de Server Actions. Es la herramienta para formularios: el usuario cambia el nombre de un proyecto y ve el nombre nuevo, no el viejo.revalidateTag(tag, 'max')marca las entradas como caducadas. La siguiente petición sigue recibiendo la copia vieja mientras se genera una fresca en segundo plano. Funciona en Server Actions y en Route Handlers. Es la herramienta para webhooks y publicaciones desde el CMS, donde unos segundos de contenido viejo no cuestan nada. El segundo argumento ahora es obligatorio; la forma con un solo argumento está obsoleta.refresh()no toca ninguna entrada de la caché. Desde una Server Action le pide al router del cliente que vuelva a cargar la página actual, para datos que nunca estuvieron en caché.
'use server'
import { updateTag } from 'next/cache'
export async function renameProject(id: string, name: string) {
await db.project.update({ where: { id }, data: { name } })
updateTag(`project-${id}`)
}import { revalidateTag } from 'next/cache'
export async function POST(request: Request) {
const { slug } = await request.json()
revalidateTag(`post-${slug}`, 'max')
return Response.json({ ok: true })
}El trabajo de diseño de verdad está en las etiquetas. cacheTag acepta varios valores, así que conviene etiquetar cada entrada con su colección (projects) y con su registro (project-42). Una lista se invalida con la primera y una página de detalle con la segunda. Las etiquetas distinguen mayúsculas y minúsculas y tienen un límite de 256 caracteres; una etiqueta más larga nunca se asigna, así que invalidarla no hace nada y no da ningún error.
¿Y qué papel le queda a fetch?
Con Cache Components activo, fetch no se cachea salvo que se ejecute dentro de un scope 'use cache'. La guía de migración lleva cache: 'force-cache' a una función cacheada, next.revalidate a cacheLife y next.tags a cacheTag. unstable_cache sigue funcionando, así que una base de código grande puede migrar las funciones de datos de una en una.
Hay una diferencia que se escapa con facilidad. La caché de fetch y unstable_cache pueden sobrevivir a un deploy; las entradas de 'use cache' no, porque el ID de la build está en la clave. Si haces diez deploys al día contra una API externa lenta, cada deploy arranca en frío. Para datos que deben persistir entre deploys, la documentación remite a la caché de fetch.
¿Dónde se guarda la caché en tiempo de ejecución?
Por defecto, en memoria: un almacén LRU dentro del proceso del servidor. Lo útil que resulte depende de dónde alojes la app. En un servidor siempre encendido, las entradas se mantienen entre peticiones y cacheMaxMemorySize limita su tamaño. En serverless, cada petición puede caer en una instancia distinta, así que las entradas creadas en tiempo de ejecución a menudo se pierden; la caché generada en la build sigue alimentando la shell estática. Comparamos esos compromisos en self-hosting de Next.js con Docker o Vercel.
Dos variantes de la directiva cubren los huecos. 'use cache: remote' guarda las entradas en un cache handler que pone la plataforma, como Redis o un almacén KV, y funciona incluso después de leer datos de la petición. Cuesta un viaje de red en cada lectura y, normalmente, una tarifa de la plataforma. 'use cache: private' puede leer cookies y cabeceras y guarda los resultados solo en la memoria del navegador, nunca en el servidor. La documentación lo reserva para requisitos de compliance o para código que no se puede refactorizar para pasar los datos de la petición como argumento.
Cache Components exige además el runtime de Node.js. Una ruta que todavía exporta runtime = 'edge' tiene que migrarse antes de activar el flag, y eso zanja una de las preguntas de nuestra comparación entre edge runtime y Node runtime para cualquier proyecto App Router que adopte el modelo.
¿Qué relación hay entre la caché y el Partial Prerendering?
Son el mismo mecanismo visto desde dos lados. En la build, Next.js renderiza todo lo que puede: el markup estático y cada scope cacheado con una duración suficiente. Esa salida se convierte en la shell que sirve la CDN. Los datos sin caché y las APIs ligadas a la petición viven dentro de boundaries Suspense y llegan en streaming en la misma respuesta. Elegir un cacheLife es elegir cuánta página llega al instante. La parte de las rutas está en nuestra guía sobre cómo estructurar rutas para el Partial Prerendering.
Las reglas que seguimos en una base de código SaaS
- Cachear funciones de datos, no páginas. Un
'use cache'al principio de un archivo de página cachea todo lo que cuelga de él, incluido lo que alguien añada el trimestre que viene. UngetProject(id)cacheado es tan acotado como su nombre. cacheLifeen cada scope. Las cachés anidadas sin una duración explícita heredan la duración corta de una caché interior; cuando pasa, Next.js lanza un error durante el prerender, y una duración declarada lo evita desde el principio.- Etiquetas por colección y por registro. Dos etiquetas por entrada cubren la lista y la página de detalle.
updateTagen las actions,revalidateTag(tag, 'max')en los webhooks. Quien hizo el cambio lo ve al momento; los demás lo ven en su siguiente petición. Es el mismo reparto que usamos en nuestros patrones de Server Actions.- Los datos de la petición se quedan fuera. La cookie se lee en la página y el ID del equipo se pasa a la función cacheada.
'use cache: remote'solo cuando el origen no aguanta la carga. Cada lectura pasa a ser una llamada de red: se acepta más latencia a cambio de menos consultas a la base de datos.
Cuándo el modelo cuesta más de lo que ahorra
Activar cacheComponents en una app existente es una migración. Todo pasa a ser dinámico hasta que lo marcas, así que un sitio que dependía de los valores por defecto antiguos primero va más lento y luego más rápido. La validación de la build señala cada ruta que lee datos de la petición fuera de un boundary Suspense, y en una base de código App Router grande resolverlos es trabajo de verdad, ruta a ruta. Una app enteramente personal, sin datos compartidos, gana poco: la caché no tiene nada que compartir entre peticiones. En cambio, para una web de marketing con contenido que cambia cada semana, el modelo encaja bien: cacheLife('days') en las funciones de contenido, una etiqueta por entrada y un webhook que llama a revalidateTag en cada publicación.
Preguntas frecuentes
Con el handler por defecto, no. Las entradas viven en la memoria de cada instancia, y en serverless una petición puede caer en cualquier instancia, así que las entradas creadas en tiempo de ejecución a menudo se pierden entre peticiones. La salida cacheada en la build sigue llegando a todos los visitantes a través de la shell estática. Para compartir entradas entre instancias hace falta 'use cache: remote' con un cache handler como Redis o un almacén KV, y asumir un viaje de red en cada lectura.
Sí, si pasas el identificador del usuario como argumento. Lee la cookie o la sesión fuera de la función cacheada y llama a algo como getDashboard(userId); el argumento entra en la clave de caché, así que cada usuario tiene su propia entrada. Nunca leas cookies() dentro del scope cacheado: lanza un error. Si el código no se puede refactorizar así, 'use cache: private' cachea por usuario solo en la memoria del navegador, nunca en el servidor.
No. unstable_cache sigue funcionando con Cache Components, así que puedes dejarlo y migrarlo más adelante. Los fetch que dependían de cache: 'force-cache' o de next.revalidate sí piden una decisión, porque con el flag activo fetch no se cachea salvo que se ejecute dentro de un scope 'use cache'. Lo habitual es ir función de datos a función de datos: se envuelve en 'use cache', el valor de revalidate pasa a cacheLife y las etiquetas a cacheTag. Ten en cuenta que las entradas de 'use cache' se vacían en cada deploy, mientras que la caché de fetch puede sobrevivir: importa mucho con APIs externas lentas.
updateTag dentro de una Server Action, cuando quien hizo el cambio tiene que verlo en la pantalla siguiente, como al guardar un formulario. Hace caducar las entradas al momento y la siguiente lectura espera a los datos frescos. revalidateTag(tag, 'max') cuando unos segundos de contenido viejo son aceptables, o cuando no estás en una Server Action, como en un webhook dentro de un Route Handler: los visitantes siguen recibiendo la copia cacheada mientras se genera una fresca. Llamar a revalidateTag con un solo argumento está obsoleto y equivale a una caducidad inmediata.
Servicios relacionados
Artículos relacionados
Web
WebReact Server Components en producción: 6 fallos a evitar en 2026
El 45% de los desarrolladores React ya usa Server Components, pero en el State of React 2025 son la tercera función menos querida. Seis fallos lo explican.
Web