Guía completa del mecanismo de caché en Next.js: cuándo usar revalidate correctamente

En pantalla siguen apareciendo esos «datos viejos» tras la vigésima recarga. Hace diez minutos cambiaste el título a mano en la base de datos, pero la página no se actualiza. Abres el código: revalidate: 60 está clarísimo. Lo borras y pones revalidate: 10. Reinicias el servidor. Recargas. Sigue igual.
El mecanismo de caché de Next.js puede ser la parte más frustrante del framework. Cuatro capas de caché, tres métodos revalidate y un cambio rompedor de la 14 a la 15. ¿Creías que con revalidate: 60 bastaba? Puede ser Router Cache, Full Route Cache sin invalidar, o que estés probando en desarrollo.
Este artículo te lo explica de forma directa:
- Cuántas capas de caché tiene Next.js y qué hace cada una
- Diferencias entre revalidatePath, revalidateTag y updateTag, y cuál usar
- Cómo diagnosticar capa por capa cuando los datos no se actualizan
Si alguna vez te has quedado atascado con datos que no cambian, revalidate que «no funciona» o no saber qué API usar, los próximos 12 minutos pueden ahorrarte varias noches en vela.
Por qué la caché de Next.js es tan compleja
¿Por qué tantas capas?
Siendo honestos, la primera vez que vi cuatro capas de caché en Next.js, mi cara debió parecerse a la tuya ahora. ¿Request Memoization? ¿Full Route Cache? ¿No puede ser más simple?
Pero pensándolo bien, cada capa resuelve un problema de rendimiento distinto:
- ¿Diez componentes en tu árbol piden datos de usuario y mandas diez peticiones?
- ¿La lista del blog cambia pocas veces al día y regeneras en cada visita?
- ¿El usuario pulsa atrás y le haces esperar otra carga?
Cada capa tiene su responsabilidad. El problema es que se influyen entre sí. Por eso cambias datos y no sabes qué caché limpiar.
Next.js 14 vs 15: una revolución en caché
A finales de 2024, Next.js 15 trajo una noticia grande: fetch ya no se cachea por defecto.
Antes (14):
fetch(url) // caché por defecto, equivalente a cache: 'force-cache'
Ahora (15):
fetch(url) // sin caché por defecto, equivalente a cache: 'no-store'
Tras actualizar, muchos vieron caer el rendimiento: lo que antes se cacheaba solo ya no. En el foro de Vercel hubo quejas, pero el argumento oficial fue: «Explícito mejor que implícito; la caché debe ser elección del desarrollador, no comportamiento por defecto».
Suena razonable. Para proyectos existentes, es un cambio rompedor.
Panorama de las cuatro capas
En resumen, los datos van del servidor al navegador pasando por estas cuatro capas:
-
Request Memoization (memoización de peticiones)
Ámbito: ciclo de renderizado de una sola petición
Responsable: React -
Data Cache (caché de datos)
Ámbito: servidor, persistente entre peticiones
Responsable: Next.js -
Full Route Cache (caché de ruta completa)
Ámbito: servidor, rutas estáticas
Responsable: Next.js -
Router Cache (caché del enrutador)
Ámbito: memoria del navegador en el cliente
Responsable: Next.js
El flujo de datos es más o menos así:
Usuario visita → Router Cache (cliente) → Full Route Cache (servidor)
↓
Data Cache → Request Memoization → fuente de datos
Tu revalidate afecta sobre todo a Data Cache y Full Route Cache. Router Cache requiere router.refresh() o recarga forzada.
Por eso a veces haces revalidate en el servidor, los datos sí cambian, pero al refrescar en el cliente sigues viendo lo viejo: Router Cache sigue activa.
En el siguiente capítulo desglosamos las cuatro capas: qué hace cada una, cuándo entra en juego y cuándo expira.
Las cuatro capas de caché en detalle
Request Memoization (memoización de peticiones)
¿Qué es?
Es una característica de React 18, no algo exclusivo de Next.js. En un ciclo de renderizado, si varios componentes hacen la misma petición GET, React la fusiona en una sola.
Ejemplo:
// app/page.tsx
async function UserProfile() {
const user = await fetch('https://api.example.com/user/123')
return <div>{user.name}</div>
}
async function UserAvatar() {
const user = await fetch('https://api.example.com/user/123') // misma petición
return <img alt="" src={user.avatar} />
}
export default function Page() {
return (
<>
<UserProfile />
<UserAvatar />
</>
)
}
Ambos componentes piden la misma URL, pero solo se envía una petición. React recuerda el primer resultado y reutiliza la caché en la segunda.
Ciclo de vida muy corto
Esta caché solo vale durante un renderizado. Al terminar, se vacía. En la siguiente visita del usuario, peticiones nuevas.
Notas
- Solo en Server Components
- Solo peticiones GET; POST, PUT, etc. no se memoizan
- En desarrollo puede no notarse, porque cada cambio de código re-renderiza
¿Cuándo te importa?
Probablemente nunca tengas que gestionarla. Es optimización automática de React, sin control manual. La menciono para que sepas: si repites el mismo fetch en varios componentes, no harás varias peticiones.
Data Cache (caché de datos)
Aquí está lo importante
Data Cache es el núcleo de la caché en Next.js. Guarda resultados de fetch en el sistema de archivos del servidor, persistente entre peticiones, usuarios e incluso despliegues.
Next.js 14 vs 15: mundos aparte
Next.js 14:
fetch('https://api.example.com/posts')
// equivalente a
fetch('https://api.example.com/posts', { cache: 'force-cache' })
// resultado: datos cacheados hasta revalidate manual
Next.js 15:
fetch('https://api.example.com/posts')
// equivalente a
fetch('https://api.example.com/posts', { cache: 'no-store' })
// resultado: nueva petición cada vez, sin caché
Si quieres cachear (Next.js 15)
Opción 1: en una petición
fetch('https://api.example.com/posts', {
cache: 'force-cache',
next: { revalidate: 3600 } // revalidar en 1 hora
})
Opción 2: en toda la ruta
// app/blog/page.tsx
export const revalidate = 3600
export default async function BlogPage() {
const posts = await fetch('https://api.example.com/posts')
// ...
}
¿Cuándo expira?
Data Cache expira cuando:
- Llega el tiempo de
revalidate(p. ej. 3600 s) - Llamas a
revalidatePath()orevalidateTag() - Vuelves a desplegar la aplicación
¿Varios fetch con distinto revalidate?
Si una página tiene varios fetch con tiempos distintos, Next.js toma el más corto como tiempo de revalidación de la página.
async function Page() {
const posts = await fetch('...', { next: { revalidate: 60 } }) // 60 s
const user = await fetch('...', { next: { revalidate: 3600 } }) // 1 h
// en la práctica, la página se revalida cada 60 s
}
Full Route Cache (caché de ruta completa)
Caché a nivel HTML
Si Data Cache guarda datos, Full Route Cache guarda el HTML completo de la página y el RSC Payload (datos serializados de React Server Components).
¿Cuándo se cachea?
Solo rutas con renderizado estático. Es decir, páginas cuyo contenido se puede determinar en build.
Si usas lo siguiente, la ruta pasa a renderizado dinámico y no se cachea:
cookies()headers()searchParams- Funciones inestables (
Math.random(),Date.now(), etc.)
¿Cómo saber si es estática o dinámica?
Ejecuta npm run build; en la terminal verás:
Route (app) Size First Load JS
┌ ○ / 5 kB 87 kB
├ ● /blog 1 kB 88 kB
└ ƒ /api/user 0 kB 87 kB
○ (Static) renderizado estático como HTML
● (SSG) HTML estático + JSON (getStaticProps)
ƒ (Dynamic) renderizado bajo demanda en el servidor
○ o ● = estática, se cachea. ƒ = dinámica, no se cachea.
Forzar estático o dinámico
// forzar estático
export const dynamic = 'force-static'
// forzar dinámico
export const dynamic = 'force-dynamic'
¿Cuándo expira?
Full Route Cache expira cuando:
- Expira Data Cache (datos cambian → hay que re-renderizar)
- Llamas a
revalidatePath('/blog') - Vuelves a desplegar
Router Cache (caché del enrutador)
Truco en el cliente
Router Cache vive en la memoria del navegador. Tras visitar una página, Next.js guarda su contenido en el cliente; al volver atrás o navegar de nuevo, usa la caché sin pedir al servidor.
Prefetch
Con <Link href="/about">, cuando el enlace entra en el viewport, Next.js prefetch el contenido de /about y lo guarda en Router Cache. Al hacer clic, la transición es instantánea.
Ciclo de vida (Next.js 14)
- Rutas estáticas: 5 minutos
- Rutas dinámicas: 30 segundos
Cambios en Next.js 15
Next.js 15 no activa Router Cache por defecto (o el tiempo es muy corto). Para activarla, configura next.config.js:
// next.config.js
module.exports = {
experimental: {
staleTimes: {
dynamic: 30, // rutas dinámicas, 30 s
static: 180, // rutas estáticas, 180 s
},
},
}
¿Cuándo expira?
- Al cumplirse el tiempo de caché
- Recarga forzada del usuario (Ctrl+Shift+R)
- Llamada a
router.refresh()
¿Por qué parece que revalidate no funciona?
A menudo llamas a revalidatePath en el servidor, los datos sí cambian, pero al refrescar el usuario sigue viendo lo viejo. Casi seguro Router Cache aún no expiró.
Soluciones:
- Recarga forzada (no puedes exigírsela al usuario)
- Tras actualizar datos,
router.refresh()(en componente cliente) - Acortar el tiempo de Router Cache
Análisis completo de los métodos revalidate
Ya vimos las cuatro capas. Ahora lo práctico: cómo invalidar la caché.
Next.js ofrece varios métodos revalidate; cada uno encaja en escenarios distintos. Entender las diferencias te ahorra horas de depuración.
Revalidate basado en tiempo (Time-based)
La forma más habitual
Revalidate por tiempo = «cada X segundos, volver a obtener datos». Es el corazón de ISR (Incremental Static Regeneration).
Dos formas de configurarlo
Opción 1: en el fetch
const res = await fetch('https://api.example.com/posts', {
next: { revalidate: 3600 } // 3600 s = 1 h
})
Opción 2: en la ruta
// app/blog/page.tsx
export const revalidate = 3600
export default async function BlogPage() {
const posts = await fetch('https://api.example.com/posts')
return <PostList posts={posts} />
}
Cómo funciona (ISR)
Supón revalidate: 3600:
- Primer usuario → se genera HTML estático, cacheado 1 h
- Durante esa hora, todos ven ese HTML (muy rápido)
- Tras 1 h, el siguiente usuario → sigue viendo el HTML viejo (sin esperar)
- Mientras tanto, Next.js regenera el HTML en segundo plano
- Cuando termina, los siguientes usuarios ven contenido nuevo
Esto es stale-while-revalidate (servir contenido caducado mientras revalidas). Ventaja: el usuario no espera. Inconveniente: alguien siempre ve datos desactualizados.
Casos de uso
- Lista de artículos (actualizaciones cada hora)
- Portada de noticias (cada 30 min)
- Catálogo de productos (una vez al día)
Problema 1: no funciona en desarrollo
Muchos se quejan de que revalidate no hace nada. Lo primero: ¿pruebas en desarrollo o en producción?
En desarrollo (npm run dev), Next.js desactiva casi toda la caché y re-renderiza en cada petición. Debes probar en producción:
npm run build
npm start
Problema 2: varios fetch con tiempos distintos
Si una página tiene varios fetch con revalidate distinto, Next.js toma el mínimo para la página. Detalle: Data Cache respeta el tiempo de cada fetch.
async function Page() {
// este fetch se revalida cada 60 s
const posts = await fetch('...', { next: { revalidate: 60 } })
// este cada 3600 s
const user = await fetch('...', { next: { revalidate: 3600 } })
}
La página se re-renderiza cada 60 s, pero los datos de user pueden cachearse 1 h: durante la primera hora la página se regenera cada minuto con user igual; tras 1 h, user también se actualiza.
Suena enrevesado, pero el diseño es razonable.
Revalidate bajo demanda: revalidatePath
Actualización disparada por el usuario
Revalidate por tiempo es un temporizador; revalidatePath es un botón: cuando ocurre un evento (p. ej. publicar un artículo), invalidas la caché a mano.
Uso
// app/actions.ts
'use server'
import { revalidatePath } from 'next/cache'
export async function publishPost(formData) {
// lógica de publicación
await db.posts.create({ ... })
// invalidar caché de la lista del blog
revalidatePath('/blog')
}
En el componente cliente:
// app/components/PublishButton.tsx
'use client'
import { publishPost } from '@/app/actions'
export function PublishButton() {
return (
<form action={publishPost}>
<button type="submit">Publicar artículo</button>
</form>
)
}
Tipos de ruta
revalidatePath admite tipo de ruta:
// solo la página /blog
revalidatePath('/blog', 'page')
// todas las páginas bajo /blog (/blog/post-1, /blog/post-2, etc.)
revalidatePath('/blog', 'layout')
Importante: no regenera al instante
Muchos creen que tras revalidatePath la página se regenera de inmediato. No es así.
revalidatePath solo marca la caché como inválida. La regeneración ocurre en la siguiente visita.
Flujo:
- Llamas a
revalidatePath('/blog') - Se vacía la caché
- El siguiente usuario en
/blog→ ahí se regenera el HTML (espera) - Los siguientes ven contenido nuevo
Casos de uso
- Tras publicar contenido, refrescar listas
- Tras cambiar configuración de admin, refrescar páginas relacionadas
- Tras enviar un formulario, refrescar la página actual
Revalidate bajo demanda: revalidateTag
Control de invalidación más flexible
revalidatePath invalida por ruta; revalidateTag invalida por etiqueta. Etiquetas en los datos e invalidas en lote.
Uso
Paso 1: etiquetar fetch
const posts = await fetch('https://api.example.com/posts', {
next: {
revalidate: 3600,
tags: ['posts'] // etiqueta 'posts'
}
})
const authors = await fetch('https://api.example.com/authors', {
next: {
revalidate: 3600,
tags: ['posts', 'authors'] // varias etiquetas
}
})
Paso 2: invalidar por etiqueta
'use server'
import { revalidateTag } from 'next/cache'
export async function publishPost() {
await db.posts.create({ ... })
// invalida todo lo marcado con 'posts'
revalidateTag('posts')
}
Estrategia stale-while-revalidate con profile=“max”
Next.js 15 recomienda profile="max":
revalidateTag('posts', { profile: 'max' })
Con profile="max":
- Marca como caducado pero no borra la caché al instante
- En la siguiente visita → datos viejos (rápido)
- En segundo plano obtiene datos nuevos
- Cuando están listos, las peticiones siguientes ven datos nuevos
Mejor que el comportamiento por defecto: el usuario no espera.
revalidatePath vs revalidateTag
| Dimensión | revalidatePath | revalidateTag |
|---|---|---|
| Granularidad | Por ruta | Por etiqueta de datos |
| Entre páginas | Solo rutas concretas | Varias páginas |
| Precisión | Gruesa | Fina |
| Complejidad | Simple | Requiere planificar etiquetas |
¿Cuándo usar Tag?
Cuando los mismos datos alimentan varias páginas.
Ejemplo, un blog:
- Lista
/blog - Detalle
/blog/[slug] - Autor
/author/[id] - Módulo «últimos artículos» en inicio
Cuatro páginas usan «datos de artículos». Con revalidatePath:
revalidatePath('/blog')
revalidatePath('/blog/[slug]')
revalidatePath('/author/[id]')
revalidatePath('/')
Con etiqueta posts en los datos:
revalidateTag('posts')
Invalidas todo lo que usa esa etiqueta. Mucho más simple.
Novedad: updateTag (Next.js 15)
Invalidación inmediata, no diferida
updateTag es nuevo en Next.js 15. A diferencia de revalidateTag: borra la caché al instante, no solo la marca como caducada.
'use server'
import { updateTag } from 'next/cache'
export async function updateUserProfile(userId, newData) {
await db.users.update({ where: { id: userId }, data: newData })
// invalidar caché de usuario al instante
updateTag(`user-${userId}`)
}
Diferencias con revalidateTag
| Dimensión | revalidateTag | updateTag |
|---|---|---|
| Forma de invalidar | Marca caducado; actualiza en segundo plano en la siguiente visita | Borra caché al instante |
| Siguiente visita | Datos viejos + fetch en background | Bloquea hasta datos nuevos |
| Restricción | Cualquier contexto | Solo Server Actions |
| Caso típico | Rendimiento general | «Leer tu propia escritura» |
¿Qué es «leer tu propia escritura»?
El usuario cambia el apodo en su perfil, guarda y la página debe mostrar el apodo nuevo de inmediato, no el viejo mientras se actualiza en background.
Ahí encaja updateTag:
export async function updateProfile(formData) {
const userId = getCurrentUserId()
await db.users.update({
where: { id: userId },
data: { nickname: formData.get('nickname') }
})
// invalidación inmediata para leer datos frescos
updateTag(`user-${userId}`)
revalidatePath('/profile')
}
Novedad: directiva use cache (Next.js 15)
Declarar caché explícitamente
Next.js 15 añade 'use cache' para marcar funciones que deben cachearse.
Uso
'use cache'
export async function getPopularPosts() {
const posts = await db.posts.findMany({
orderBy: { views: 'desc' },
take: 10
})
return posts
}
Con cacheTag
import { unstable_cacheTag as cacheTag } from 'next/cache'
'use cache'
export async function getPostsByAuthor(authorId) {
cacheTag('posts', `author-${authorId}`)
return await db.posts.findMany({
where: { authorId }
})
}
Luego invalidas con revalidateTag:
revalidateTag(`author-${authorId}`)
¿Por qué hace falta?
En Next.js 15, fetch no cachea por defecto. Si no quieres consultar la base en cada petición, declaras caché. use cache deja la intención clara.
Guía de diagnóstico de problemas habituales
Teoría hecha. Lo práctico: cuando la caché falla, cómo diagnosticar.
Cuatro problemas muy comunes, cada uno con pasos y soluciones.
Problema 1: revalidate configurado pero no funciona
Síntoma
Tienes export const revalidate = 60, pero tras 10 minutos la página sigue con datos viejos.
Pasos
1. Confirma que pruebas en producción
El error más habitual. En desarrollo (npm run dev) casi no hay caché.
Prueba así:
npm run build
npm start
2. Comprueba si la ruta es dinámica
Ejecuta npm run build y mira la salida:
Route (app) Size
├ ○ /blog 1 kB ← estática, se cachea
└ ƒ /profile 2 kB ← dinámica, no se cachea
Si la ruta es ƒ (dinámica), revalidate no aplica: las dinámicas no se cachean.
Código que fuerza renderizado dinámico:
// todo esto hace la ruta dinámica
import { cookies } from 'next/headers'
import { headers } from 'next/headers'
export default function Page({ searchParams }) { // searchParams
const cookieStore = cookies() // cookies
// ...
}
Solución:
- Si no necesitas dinámico, quita ese código
- Si lo necesitas, no esperes
revalidate; usa revalidate bajo demanda
3. Revisa la versión de Next.js
14 y 15 difieren en el comportamiento por defecto. Tras pasar a 15, mucho de lo que se cacheaba ya no.
Solución (Next.js 15):
// activar caché explícitamente
fetch(url, {
cache: 'force-cache',
next: { revalidate: 60 }
})
// o directiva use cache
'use cache'
export async function getData() {
// ...
}
4. Comprueba interferencia de Router Cache
Aunque el servidor tenga datos nuevos, Router Cache en el cliente puede seguir sirviendo lo viejo.
Solución:
- Recarga forzada (Ctrl+Shift+R)
- O en Next.js 15,
staleTimesmás cortos
Problema 2: datos actualizados pero la página sigue vieja
Síntoma
Cambias datos en la base o llamas a revalidatePath, pero al refrescar sigues viendo lo antiguo.
Enfoque: revisar las cuatro capas
Capa 1: Router Cache (cliente)
La caché del cliente es fácil de olvidar.
Prueba rápida:
- Ctrl+Shift+R (recarga forzada)
- Si se actualiza, el problema era Router Cache
Solución:
'use client'
import { useRouter } from 'next/navigation'
export function RefreshButton() {
const router = useRouter()
return (
<button onClick={() => router.refresh()}>
Actualizar
</button>
)
}
O en next.config.js, tiempos más cortos:
module.exports = {
experimental: {
staleTimes: {
dynamic: 0, // sin caché en rutas dinámicas
static: 30, // estáticas, 30 s
},
},
}
Capa 2: Full Route Cache (servidor)
Comprueba si la ruta es estática. Si lo es, todo el HTML está cacheado.
Prueba rápida:
# ventana de incógnito nueva
# si sigue viejo, es caché del servidor
Solución:
'use server'
import { revalidatePath } from 'next/cache'
export async function updateData() {
await db.update({ ... })
revalidatePath('/your-page')
}
Capa 3: Data Cache (servidor)
Revisa la configuración del fetch.
Prueba rápida:
Log con marca de tiempo en el fetch:
const data = await fetch(url)
console.log('Fetched at:', new Date().toISOString())
Si al refrescar la marca no cambia, estás usando caché.
Solución:
Opción 1: etiquetas e invalidación
const data = await fetch(url, {
next: { tags: ['my-data'] }
})
revalidateTag('my-data')
Opción 2: desactivar caché para probar
const data = await fetch(url, {
cache: 'no-store'
})
Capa 4: Request Memoization (servidor)
Solo dura una petición; rara vez es el problema. Si las tres capas anteriores están bien, revisa la fuente de datos.
Problema 3: ¿revalidatePath o revalidateTag?
Árbol de decisión
Hay que invalidar caché
|
├─ Solo una página
| → revalidatePath('/specific-page')
|
├─ Todas las páginas bajo una ruta
| → revalidatePath('/blog', 'layout')
|
├─ Datos compartidos en rutas distintas
| → revalidateTag('your-tag')
|
└─ Invalidación inmediata (ver cambio al instante)
→ updateTag('your-tag') (Next.js 15)
Caso práctico: blog
Páginas:
- Inicio: últimos 3 artículos
- Lista
/blog: todos - Detalle
/blog/[slug]: uno - Autor
/author/[id]: artículos del autor
Estrategia de etiquetas:
async function getPosts() {
return fetch('https://api.example.com/posts', {
next: {
revalidate: 3600,
tags: ['posts']
}
})
}
async function getPostBySlug(slug) {
return fetch(`https://api.example.com/posts/${slug}`, {
next: {
revalidate: 3600,
tags: ['posts', `post-${slug}`]
}
})
}
async function getPostsByAuthor(authorId) {
return fetch(`https://api.example.com/posts?author=${authorId}`, {
next: {
revalidate: 3600,
tags: ['posts', `author-${authorId}-posts`]
}
})
}
Al publicar:
export async function publishPost(formData) {
await db.posts.create({ ... })
revalidateTag('posts')
}
Al editar un artículo:
export async function updatePost(slug, newData) {
await db.posts.update({ where: { slug }, data: newData })
revalidateTag(`post-${slug}`)
// o también la lista
revalidateTag('posts')
}
Problema 4: tras migrar de 14 a 15 la caché dejó de funcionar
Síntoma
Tras actualizar a Next.js 15, datos que antes se cacheaban se piden en cada visita; rendimiento en caída.
Causa
Tres cambios por defecto en Next.js 15:
- fetch pasa de
force-cacheano-store - Route Handlers GET no cachean por defecto
- Router Cache desactivada por defecto
Migración
Opción 1: activar caché explícitamente (recomendado)
// antes (Next.js 14)
const data = await fetch(url)
// ahora (Next.js 15)
const data = await fetch(url, {
cache: 'force-cache',
next: { revalidate: 3600 }
})
Opción 2: directiva use cache
'use cache'
export async function getPostList() {
const posts = await db.posts.findMany()
return posts
}
Opción 3: activar Router Cache
// next.config.js
module.exports = {
experimental: {
staleTimes: {
dynamic: 30,
static: 180,
},
},
}
Antes y después:
// Next.js 14 — caché implícita
export default async function BlogPage() {
const posts = await fetch('https://api.example.com/posts')
// caché automática
}
// Next.js 15 — caché explícita
'use cache'
export default async function BlogPage() {
const posts = await fetch('https://api.example.com/posts', {
cache: 'force-cache',
next: { revalidate: 3600 }
})
}
Mi consejo:
No esperes migración en un clic. Revisa cada petición de datos y decide qué cachear y qué no. Es más trabajo, pero a largo plazo sabes qué está cacheado y qué no.
Mejores prácticas y estrategia de elección
Resumen: qué estrategia de caché usar y cuándo.
Flujo de elección de estrategia
¿Con qué frecuencia cambian tus datos?
|
├─ Casi nunca (about, ayuda)
| → generación estática, sin revalidate
| → redeploy manual al cambiar
|
├─ Actualización periódica (cada hora, día)
| → ISR + revalidate por tiempo
| → export const revalidate = 3600
|
├─ Actualización irregular (usuario publica)
| → revalidate bajo demanda
| → revalidatePath o revalidateTag
|
└─ Tiempo real (chat, datos en vivo)
→ renderizado dinámico + cache: 'no-store'
→ no cachear
Estrategia de nombres de etiquetas
Si usas revalidateTag, conviene una convención clara:
Granularidad
-
Gruesa (invalidación masiva)
posts— todos los artículosproducts— todos los productosusers— todos los usuarios
-
Media (por categoría o estado)
posts:published— publicadosposts:draft— borradoresproducts:category:electronics— electrónica
-
Fina (recurso concreto)
post:id:123— artículo 123user:profile:456— perfil del usuario 456
Convención sugerida
Espacio de nombres entity:type:id:
const post = await fetch(`/api/posts/${id}`, {
next: {
tags: [
'posts',
'posts:published',
`post:id:${id}`
]
}
})
revalidateTag('posts')
revalidateTag('posts:published')
revalidateTag(`post:id:${id}`)
Consejos de optimización
1. No cachees en exceso
Más caché no siempre es mejor:
- Inconsistencia de datos
- Depuración difícil
- Desperdicio de almacenamiento
Regla práctica:
- Datos personalizados (carrito, ajustes) → no cachear
- Datos públicos (listas, artículos) → cachear
- Datos en tiempo real (stock, usuarios online) → no cachear o TTL muy corto
2. Tiempos de revalidate razonables
revalidate: 1 implica regeneración casi continua; no tiene sentido como caché.
Recomendaciones:
- Noticias: 30–60 min
- Blog: 1–2 h
- Catálogo: 2–4 h
- Páginas estáticas: 24 h o más
3. stale-while-revalidate
En Next.js 15, profile="max" implementa esta estrategia:
revalidateTag('posts', { profile: 'max' })
El usuario siempre ve caché (rápido); el sistema actualiza en segundo plano. Buena experiencia.
4. Monitorizar aciertos de caché
En .env.local:
NEXT_PRIVATE_DEBUG_CACHE=1
En el servidor de producción verás:
○ GET /blog 200 in 45ms (cache: HIT)
○ GET /about 200 in 12ms (cache: SKIP)
Revisa periódicamente si la estrategia funciona.
Diferencias desarrollo vs producción
Recordatorio:
Desarrollo (npm run dev) y producción (npm start) se comportan muy distinto en caché.
| Característica | Desarrollo | Producción |
|---|---|---|
| Data Cache | Casi desactivada | Activa |
| Full Route Cache | Desactivada | Activa en rutas estáticas |
| Request Memoization | Activa | Activa |
| Router Cache | Activa, TTL corto | TTL completo |
Forma correcta de probar caché:
# 1. build de producción
npm run build
# 2. revisar tipos de ruta en la salida
# ○ = estática, se cachea
# ƒ = dinámica, no se cachea
# 3. servidor de producción
npm start
# 4. probar comportamiento
# visita, cambia la fuente de datos, recarga
# 5. probar revalidate
# espera el TTL y comprueba la actualización
No depures caché en desarrollo. Es la trampa más común: horas peleando con revalidate en dev cuando el entorno no refleja producción.
Conclusión
En resumen, lo esencial:
1. Entiende el papel de cada capa
No las mezcles. Router Cache es del cliente; las otras tres del servidor. revalidate afecta sobre todo a Data Cache y Full Route Cache.
2. Elige el método revalidate adecuado
- Actualización programada →
export const revalidate = 3600 - Usuario, una página →
revalidatePath('/page') - Usuario, varias páginas →
revalidateTag('tag') - Invalidación inmediata →
updateTag('tag')(Next.js 15)
3. Prueba en producción
La caché en desarrollo no es fiable. Para probar: npm run build && npm start.
4. Diagnostica capa por capa
Si los datos no cambian:
- Router Cache (cliente) → recarga forzada
- Full Route Cache (servidor) → revalidatePath
- Data Cache (servidor) → revalidateTag
- La fuente de datos
5. Explícito mejor que implícito (filosofía Next.js 15)
Next.js 15 pasó de cachear por defecto a no cachear. Debes decidir qué cachear. Más trabajo al principio, código más claro y mantenible.
Si llegaste hasta aquí, ya tienes una visión completa del mecanismo de caché en Next.js. La próxima vez que los datos no se actualicen, sabrás por dónde empezar.
La caché es compleja, pero dominarla convierte a Next.js en una de sus mejores armas. Bien usada, la app vuela; mal usada, te haces un agujero.
¡Que tu app rinda a tope y los bugs se queden en cero!
Flujo completo del mecanismo de caché en Next.js
Desde entender las cuatro capas hasta elegir el método revalidate y diagnosticar datos que no se actualizan
⏱️ Estimated time: 2 hr
- 1
Step 1: Entender las cuatro capas de caché
Cuatro capas:
1. Request Memoization (deduplicación de peticiones)
• En una misma petición, la misma URL solo se solicita una vez
• Automático, sin configuración
• Ciclo de vida: una sola petición
2. Data Cache (caché de fetch)
• Caché de peticiones fetch
• Comportamiento por defecto: Next.js 14 cachea, 15 no
• Configuración: opción cache
3. Full Route Cache (caché de ruta completa)
• Caché del HTML de toda la ruta
• Las páginas estáticas se cachean automáticamente
• Configuración: revalidate
4. Router Cache (caché de rutas en el cliente)
• Caché al navegar en el cliente
• Automático, sin configuración
• Ciclo de vida: durante la sesión
Punto clave: Router Cache es del cliente; las otras tres son del servidor. - 2
Step 2: Elegir el método revalidate adecuado
Tres métodos:
1. Actualización programada (export const revalidate)
```tsx
export const revalidate = 3600 // revalidar tras 3600 segundos
```
• Uso: contenido que se actualiza a intervalos
• Configuración: en page.tsx o layout.tsx
2. Disparado por el usuario, una página (revalidatePath)
```tsx
import { revalidatePath } from 'next/cache'
revalidatePath('/blog/post-1')
```
• Uso: tras una acción del usuario, actualizar una sola página
• Uso: en Server Action o API Route
3. Disparado por el usuario, varias páginas (revalidateTag)
```tsx
import { revalidateTag } from 'next/cache'
// añadir tag al fetch
fetch(url, { next: { tags: ['posts'] } })
// invalidar el tag al actualizar
revalidateTag('posts')
```
• Uso: tras una acción del usuario, actualizar varias páginas
• Uso: en Server Action o API Route
Recomendación:
• Actualización programada → export const revalidate
• Una página → revalidatePath
• Varias páginas → revalidateTag - 3
Step 3: Diagnosticar datos que no se actualizan
Orden de diagnóstico:
1. Comprobar si estás en entorno de desarrollo
• El comportamiento de caché en desarrollo no es fiable
• Prueba en producción: npm run build && npm start
2. Comprobar Router Cache (cliente)
• Prueba recarga forzada (Ctrl+Shift+R)
• Borra la caché del navegador
3. Comprobar Full Route Cache (servidor)
• Usa revalidatePath para limpiar
• Revisa la configuración de revalidate
4. Comprobar Data Cache (servidor)
• Usa revalidateTag para limpiar
• Revisa la opción cache del fetch
5. Comprobar la fuente de datos
• Confirma que la fuente realmente se actualizó
• Revisa los datos que devuelve la API
Problemas habituales:
• Probar en desarrollo → usa producción
• Router Cache sin limpiar → recarga forzada
• Configuración revalidate incorrecta → revisa la config
• Next.js 15 no cachea por defecto → configura cache explícitamente - 4
Step 4: Diferencias de caché entre Next.js 14 y 15
Next.js 14:
• fetch cachea por defecto (equivalente a getStaticProps)
• Para desactivar: cache: 'no-store'
Next.js 15:
• fetch no cachea por defecto
• Para activar: cache: 'force-cache'
Recomendación de migración:
• Revisa todas las llamadas fetch
• Configura explícitamente la opción cache
• Prueba el comportamiento de caché
Ejemplo:
```tsx
// Next.js 14 (caché por defecto)
fetch(url) // se cachea automáticamente
// Next.js 15 (sin caché por defecto)
fetch(url, { cache: 'force-cache' }) // hay que configurarlo
```
Punto clave: la filosofía de Next.js 15 es explícito mejor que implícito; debes decidir activamente qué datos cachear.
FAQ
¿Cuántas capas de caché tiene Next.js? ¿Qué hace cada una?
1. Request Memoization (deduplicación)
• En una misma petición, la misma URL solo se solicita una vez
• Automático, sin configuración
• Ciclo de vida: una sola petición
2. Data Cache (caché de fetch)
• Caché de peticiones fetch
• Por defecto: Next.js 14 cachea, 15 no
• Configuración: opción cache
3. Full Route Cache (caché de ruta completa)
• Caché del HTML de toda la ruta
• Páginas estáticas cacheadas automáticamente
• Configuración: revalidate
4. Router Cache (caché de rutas en el cliente)
• Caché al navegar en el cliente
• Automático, sin configuración
• Ciclo de vida: durante la sesión
Punto clave: Router Cache es del cliente; las otras tres del servidor. revalidate afecta sobre todo a Data Cache y Full Route Cache.
¿Qué diferencia hay entre revalidatePath, revalidateTag y updateTag?
• Invalida la caché de una ruta concreta
• Uso: tras acción del usuario, actualizar una página
• Uso: revalidatePath('/blog/post-1')
revalidateTag (varias páginas):
• Invalida toda la caché de un tag
• Uso: tras acción del usuario, actualizar varias páginas
• Uso: tag en fetch e invalidar el tag al actualizar
updateTag (invalidación inmediata, Next.js 15):
• Marca el tag como caducado al instante
• Uso: cuando necesitas invalidación inmediata
• Uso: updateTag('tag')
Recomendación:
• Una página → revalidatePath
• Varias páginas → revalidateTag
• Invalidación inmediata → updateTag (Next.js 15)
Nota: revalidateTag y updateTag requieren tags en fetch.
¿Por qué los datos no se actualizan aunque configuré revalidate?
1. Pruebas en entorno de desarrollo
• La caché en desarrollo no es fiable
• Usa producción: npm run build && npm start
2. Router Cache sin limpiar
• La caché del cliente sigue activa
• Prueba recarga forzada (Ctrl+Shift+R)
3. Configuración revalidate incorrecta
• Revisa el valor de revalidate
• Revisa dónde está configurado
4. Next.js 15 no cachea por defecto
• Configura cache: 'force-cache'
• Revisa la opción cache del fetch
5. La fuente de datos no se actualizó
• Confirma que la fuente cambió
• Revisa la respuesta de la API
Orden de diagnóstico:
1. ¿Entorno de desarrollo?
2. Router Cache (recarga forzada)
3. Full Route Cache (revalidatePath)
4. Data Cache (revalidateTag)
5. Fuente de datos
¿Qué diferencia hay en caché entre Next.js 14 y 15?
Next.js 14:
• fetch cachea por defecto (como getStaticProps)
• Para desactivar: cache: 'no-store'
• Comportamiento: caché implícita
Next.js 15:
• fetch no cachea por defecto
• Para activar: cache: 'force-cache'
• Comportamiento: configuración explícita
Migración:
• Revisa todas las llamadas fetch
• Configura explícitamente cache
• Prueba el comportamiento
Ejemplo:
```tsx
// Next.js 14 (caché por defecto)
fetch(url) // caché automática
// Next.js 15 (sin caché por defecto)
fetch(url, { cache: 'force-cache' }) // configuración explícita
```
Punto clave: Next.js 15 prioriza lo explícito; debes pensar qué datos cachear. Cambio rompedor en migración.
¿Cuándo usar revalidatePath y cuándo revalidateTag?
• Uso: tras acción del usuario, una sola página
• Ejemplo: editar artículo y actualizar la página de detalle
• Uso: revalidatePath('/blog/post-1')
revalidateTag (varias páginas):
• Uso: tras acción del usuario, varias páginas
• Ejemplo: publicar artículo y actualizar todas las listas
• Uso: tag en fetch e invalidar al actualizar
Recomendación:
• Solo una página → revalidatePath
• Varias páginas → revalidateTag
Ejemplo:
```tsx
// una página
revalidatePath('/blog/post-1')
// varias páginas
fetch(url, { next: { tags: ['posts'] } })
revalidateTag('posts') // invalida toda caché con tag 'posts'
```
Punto clave: revalidateTag requiere tags en fetch; es más flexible.
¿El comportamiento de caché es igual en desarrollo y producción?
Desarrollo:
• Comportamiento de caché inestable
• Puede no cachear
• No sirve para probar caché
Producción:
• Comportamiento de caché correcto
• Caché normal
• Adecuado para pruebas
Probar caché:
• Usa producción: npm run build && npm start
• No pruebes caché en desarrollo
Error habitual:
• Probar caché en desarrollo → resultados incorrectos
• Quejarse de que revalidate no funciona → suele ser el entorno
Recomendación: prueba siempre la caché en producción.
19 min de lectura · Publicado el: 19 dic 2025 · Actualizado el: 21 ago 2026
Guía completa de Next.js
Si llegaste desde búsqueda, lo más rápido es ir al artículo anterior o siguiente de esta misma serie.
Anterior
Guía de gestión de estado en Next.js: Zustand vs Jotai en la práctica
¿Redux demasiado pesado y Context con mal rendimiento? Este artículo compara Zustand y Jotai en Next.js, ofrece una guía clara de elección y buenas prácticas con App Router para elegir la solución ligera adecuada.
Parte 24 de 51
Siguiente
Guía completa de optimización de imágenes en Next.js: uso correcto del componente Image
Guía completa del componente Image de Next.js: soluciona carga lenta, errores de configuración de imágenes remotas y desplazamiento de layout. Incluye novedades de Next.js 14/15, ejemplos de código y técnicas de optimización para mejorar el rendimiento un 60-80 %.
Parte 26 de 51



Comentarios
Inicia sesión con GitHub para dejar un comentario