Diario / Desarrollo de producto web
Cómo funciona la caché en Next.js 13+ con App Router

Un repaso detallado de cómo funciona la caché en Next.js 13+, qué tipos de caché existen y cómo afectan al rendimiento y al SEO.
Qué es la caché y cómo funciona en Next.js
La caché sirve para guardar el resultado de un cálculo o de una petición y no repetirlos, de modo que la aplicación va más rápido. En Next.js se pueden cachear las páginas HTML, los datos, las peticiones, el renderizado de componentes e incluso rutas enteras.
Con la llegada del App Router (desde Next.js 13) la filosofía cambió: la caché está activada por defecto y es el desarrollador quien decide desactivarla cuando necesita un comportamiento dinámico.
La política de Next.js es esta:
"Cacheamos todo, salvo que se indique lo contrario"
Si no indica de forma explícita cache: 'no-store', next: { revalidate } o dynamic = 'force-dynamic', ocurre lo siguiente:
fetch()se guardará en caché- el layout y la página también pueden guardarse en caché
- las rutas se añadirán a la caché de rutas
- el renderizado de la página pasará a la Full Route Cache
Esto da rendimiento, pero puede provocar datos desactualizados y problemas al depurar.
Tipos de caché en Next.js

Fuente: documentación de Next.js
Full Route Cache (caché de rutas en el servidor)
Se usa con ISR (Incremental Static Regeneration, regeneración estática incremental).
- Se guarda en caché la página completa (HTML, datos de
layout.tsx,page.tsx,fetch(),headers(),cookies()). - La caché está en el servidor.
- Funciona por defecto cuando no hay comportamiento dinámico.
- Se desactiva con
revalidateorevalidateTag().
Es la mejor opción para blogs, páginas de marketing y otros contenidos que cambian poco.
Ejemplo:
export const revalidate = 60; // regenerate the page every 60 seconds
Data Cache (caché de datos en el servidor)
- Las respuestas de
fetch()en Server Components se guardan en caché. - Se controla con
next: { revalidate }ocache: 'no-store'.
Ejemplo:
await fetch('https://api.com/posts', {
next: { revalidate: 300 },
});
Request Memoization (caché por petición, en el servidor)
Es una optimización interna de Next.js: el mismo fetch() o la misma función con los mismos argumentos se ejecuta una sola vez por renderizado.
- Durante un renderizado en el servidor, Next.js hace seguimiento de las peticiones idénticas.
- Si ya se han ejecutado, el resultado se toma de la caché.
- Funciona con independencia de
cache: 'force-cache'ono-store.
Es útil para:
- DRY (no repetirse) al obtener los mismos datos en distintos componentes.
- Mejorar el rendimiento sin optimizar a mano.
- Cuando el mismo
fetchse usa tanto enlayout.tsxcomo enpage.tsx.
No sirve para:
- Reutilizar datos entre peticiones o usuarios distintos (no es una caché global).
- Saltarse la Data Cache: trabaja sobre la base de la Request Memoization.
// lib/getUser.ts
export async function getUser() {
return fetch('https://api.com/user').then(res => res.json());
}
// app/layout.tsx
export default async function Layout({ children }) {
const user = await getUser(); // Call 1
return (
<div>
<Sidebar user={user} />
{children}
</div>
);
}
// app/page.tsx
import { getUser } from '../lib/getUser';
export default async function Page() {
const user = await getUser(); // Call 2, but actually not repeated
return <p>Hello, {user.name}</p>;
}
Físicamente, el fetch se ejecutará una sola vez y el resultado se usará en los dos sitios.
Router Cache (caché en el cliente)
Es una caché que usted controla con bibliotecas como SWR, React Query y otras.
- Solo funciona con la navegación en el cliente (
<Link>,router.push()). - Next.js no controla esta caché: usted decide cómo recordar los datos.
- Es muy rápida, porque no se vuelve a renderizar nada: React simplemente monta desde la memoria.
- Pero solo existe hasta que el usuario recarga la página (F5).
Es la mejor opción para componentes interactivos, formularios y paneles de control.
Ejemplo:
'use client';
import useSWR from 'swr';
const { data } = useSWR('/api/data', fetcher);
Comparación visual de los tipos de caché
| Tipo de caché | Dónde funciona | Control | Ejemplo |
|---|---|---|---|
| Full Route Cache | Servidor (toda la ruta) | revalidate, tag | export const revalidate = 60 |
| Data Cache | Servidor (fetch) | cache, revalidate | fetch(..., { next: ... }) |
| Caché generada estáticamente | Servidor (en la compilación) | Ninguno | JSX sin datos |
| Caché en el cliente | Navegador / cliente | Usted | SWR / React Query |
Un ejemplo del día a día
Supongamos que está construyendo una tienda online:
| Página | Qué guardar | Qué tipo de caché usar |
|---|---|---|
/productos | Lista de productos, actualización cada 5 minutos | Full Route Cache (ISR) |
/api/productos | Datos de producto de la API | Data Cache (con revalidateTag) |
| Navegación SPA | Navegación rápida | Router Cache (caché en el cliente) |
Problemas habituales de la caché en App Router
Los datos se han actualizado, pero la página muestra los antiguos
fetch() usa force-cache por defecto si no indica cache: 'no-store' o next: { revalidate }. Como resultado, Next.js recuerda los datos y nunca los actualiza.
Solución:
- Añada
cache: 'no-store'para desactivar la caché - O
next: { revalidate: 60 }para ISR
fetch('https://api.com/data', { next: { revalidate: 60 } });
La URL (searchParams) ha cambiado, pero los datos son los mismos
Next.js no repite la petición de fetch si la URL es la misma. Si utiliza parámetros, añádalos directamente a la URL del fetch:
fetch(`https://api.com/posts?category=${searchParams.category}`, {
next: { revalidate: 60 }
});
Se navega con <Link>, pero los datos no se han actualizado
Se ha usado la Router Cache (caché en el cliente). Next.js no envía una petición nueva al volver a la página.
Solución:
- Fuerce la actualización de los datos con
router.refresh() - Use
cache: 'no-store'si los datos deben estar siempre al día
revalidatePath() o revalidateTag() no funciona
- No se ha creado la caché (por ejemplo, por
no-store) - No usa
tagsen el fetch - Llama a
revalidatePath()fuera de una Server Action
Solución:
- Compruebe que el fetch usa
next: { tags: [...] } - Llame a
revalidateTag('tag')en una función del servidor
El layout está en caché y no se actualiza
layout.tsx también se cachea como RSC con revalidate, y si los datos de su interior están en caché, no se actualizarán hasta el siguiente ISR.
Solución:
- Use
cache: 'no-store'en los fetch de layout.tsx si contiene, por ejemplo, el perfil del usuario - O mueva el fetch del layout a la página (o a un componente cliente con SWR)
Comportamiento inesperado en modo desarrollo (dev)
En next dev muchas cachés están desactivadas o funcionan de otra manera, y revalidate no funciona de forma fiable en modo desarrollo.
Consejo:
Pruebe la caché con next build && next start o en una vista previa de Vercel.
La combinación de cookies(), headers(), params y fetch() rompe la caché
Cuando utiliza elementos dinámicos como:
cookies()
headers()
useSearchParams()
generateMetadata() con valores dinámicos
Next.js desactiva automáticamente la caché de la página y pasa a dynamic = 'force-dynamic'. Lista de comprobación: cómo evitar problemas con la caché
Defina siempre de forma explícita el comportamiento del fetch:
- No olvide incluir los parámetros de consulta directamente en la URL del fetch
// no cache at all
fetch(..., { cache: 'no-store' })
// ISR (revalidate every 60 seconds)
fetch(..., { next: { revalidate: 60 } })
// manual invalidation via tag
fetch(..., { next: { tags: ['products'] } })
- No olvide poner los parámetros de consulta directamente en la URL que pasa a fetch
- Pruebe con
next build && next start; de lo contrario puede tener una falsa sensación de seguridad - Si necesita invalidar los datos a mano, use
revalidatePath()orevalidateTag()
Si la caché de su proyecto con App Router ya está en producción y da problemas, ese es el tipo de trabajo que asumo como desarrollador freelance de Next.js: auditorías de caché, correcciones y migraciones a App Router.