Almacenamiento en caché
Comprende las cuatro capas de almacenamiento en caché en Next.js: memoización de solicitudes, caché de datos, caché de ruta completa y caché del enrutador.
Receta
Tarjeta de receta de referencia rápida -- lista para copiar y pegar.
// Capa 1: Memoización de solicitudes (automática para fetch, manual para otros)
// La misma URL de fetch se deduplica dentro de un único paso de renderizado
const data = await fetch("https://api.example.com/user/1"); // llamada en 3 componentes, solo 1 solicitud
// Memoización manual para llamadas que no son de fetch
import { cache } from "react";
export const getUser = cache(async (id: string) => {
return db.user.findUnique({ where: { id } });
});
// Capa 2: Caché de datos (persiste entre solicitudes)
await fetch(url, { next: { revalidate: 3600 } }); // almacenado en caché por 1 hora
await fetch(url, { cache: "no-store" }); // omitir caché de datos
await fetch(url, { next: { tags: ["users"] } }); // etiquetar para invalidación
// Capa 3: Caché de ruta completa (páginas estáticas en tiempo de compilación)
// Automático para rutas sin datos dinámicos
// Capa 4: Caché del enrutador (lado del cliente, en memoria)
// Automático para prefetch de <Link> y navegación atrás/adelanteCuándo usarlo: necesitas entender por qué tus datos están obsoletos, por qué una página no se está actualizando o cómo optimizar el rendimiento aprovechando la capa de caché correcta.
Ejemplo de funcionamiento
// lib/data.ts -- demostrando todas las capas de almacenamiento en caché
import { cache } from "react";
import { db } from "@/lib/db";
// ---- Capa 1: Memoización de solicitudes ----
// Esta función puede ser llamada desde múltiples Server Components
// en el mismo paso de renderizado -- solo se ejecuta una consulta de BD
export const getCurrentUser = cache(async () => {
const session = await getSession();
if (!session) return null;
return db.user.findUnique({ where: { id: session.userId } });
});
// ---- Capa 2: Caché de datos ----
export async function getProducts() {
const res = await fetch("https://api.example.com/products", {
next: { revalidate: 600, tags: ["products"] },
});
return res.json() as Promise<Product[]>;
}
// Sin almacenamiento en caché -- siempre fresco
export async function getCartItems(userId: string) {
const res = await fetch(`https://api.example.com/cart/${userId}`, {
cache: "no-store",
});
return res.json() as Promise<CartItem[]>;
}// app/layout.tsx -- getCurrentUser llamada aquí...
import { getCurrentUser } from "@/lib/data";
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
const user = await getCurrentUser(); // Solicitud 1 (llamada real a BD)
return (
<html lang="en">
<body>
<nav>
<span>{user?.name ?? "Guest"}</span>
</nav>
{children}
</body>
</html>
);
}// app/dashboard/page.tsx -- ...y también llamada aquí (deduplicada)
import { getCurrentUser } from "@/lib/data";
export default async function DashboardPage() {
const user = await getCurrentUser(); // Solicitud 2 (memoizada -- sin llamada a BD)
return <h1>Bienvenido de vuelta, {user?.name}</h1>;
}Lo que esto demuestra:
React.cachededuplicando una llamada a base de datos entre dos Server Components en el mismo renderizadonext.revalidatecolocando los resultados de fetch en la Caché de datoscache: "no-store"omitiendo la Caché de datos para datos específicos del usuario- El layout y la página ambos llamando a
getCurrentUser()sin desencadenar dos consultas
Análisis profundo
Cómo funciona
- Memoización de solicitudes se ejecuta durante un único renderizado de servidor. Cuando se llama a
fetchcon la misma URL y opciones, React devuelve el resultado memoizado. Para llamadas que no son de fetch (base de datos, ORM), envuelve la función conReact.cache(). La memoización se borra después de que se completa el renderizado. - Caché de datos persiste los datos obtenidos entre solicitudes e implementaciones en el servidor. Se indexa por la URL de fetch y opciones. Respeta
next.revalidatepara expiración basada en tiempo enext.tagspara invalidación bajo demanda víarevalidateTag(). - Caché de ruta completa almacena el HTML completo y la carga de RSC para rutas renderizadas estáticamente en tiempo de compilación. Cuando un usuario solicita una página estática, la versión en caché se sirve instantáneamente. Las rutas dinámicas (aquellas que usan
cookies(),headers(),searchParamsocache: "no-store") están excluidas. - Caché del enrutador es un caché lado del cliente en memoria que almacena segmentos de ruta prefetcheados. Cuando el usuario navega con
<Link>, la página siguiente puede que ya esté en caché. El caché del enrutador tiene diferentes tiempos de vida: 30 segundos para páginas dinámicas, 5 minutos para páginas estáticas (estos son valores predeterminados y configurables en Next.js 15+).
Variaciones
Optar por no usar almacenamiento en caché para una ruta:
// app/real-time/page.tsx
export const dynamic = "force-dynamic"; // omite Full Route Cache
export const fetchCache = "force-no-store"; // omite Data Cache para todos los fetchesUsando unstable_cache para datos que no son de fetch:
import { unstable_cache } from "next/cache";
const getCachedPosts = unstable_cache(
async () => db.post.findMany(),
["all-posts"], // partes de la clave de caché
{
tags: ["posts"],
revalidate: 3600,
}
);Controlando el comportamiento de Router Cache:
// next.config.ts
import type { NextConfig } from "next";
const config: NextConfig = {
experimental: {
staleTimes: {
dynamic: 0, // no cachear páginas dinámicas en el cliente
static: 300, // cachear páginas estáticas por 5 minutos
},
},
};
export default config;Notas de TypeScript
// React.cache preserva la firma de la función
import { cache } from "react";
const getUser = cache(
async (id: string): Promise<User | null> => {
return db.user.findUnique({ where: { id } });
}
);
// getUser: (id: string) => Promise<User | null>
// Tipos de configuración de segmento
export const dynamic: "auto" | "force-dynamic" | "force-static" | "error" = "auto";
export const revalidate: number | false = 60;
export const fetchCache: "auto" | "default-cache" | "only-cache" |
"force-cache" | "force-no-store" | "default-no-store" | "only-no-store" = "auto";Trampa
-
Confundir memoización de solicitudes con Caché de datos -- La memoización de solicitudes es por renderizado y efímera; la Caché de datos persiste entre solicitudes. Solución: usa
React.cache()para deduplicación por solicitud ynext.revalidate/next.tagspara almacenamiento en caché persistente. -
Caché del enrutador sirviendo páginas antiguas -- Después de una mutación, el Caché del enrutador lado del cliente puede seguir sirviendo la página anterior durante navegación atrás/adelante. Solución: llama a
router.refresh()en el Client Component después de una Server Action, o configurastaleTimespara reducir la duración del caché lado del cliente. -
cache: "force-cache"no siempre es el valor predeterminado -- En Next.js 15+, el valor predeterminado escache: "auto", que puede variar según el contexto (funciones dinámicas, configuración de ruta). Solución: sé explícito sobre el comportamiento de almacenamiento en caché estableciendocacheonext.revalidateen cada fetch. -
Colisiones de clave
unstable_cache-- Si dos llamadas aunstable_cachecomparten las mismas partes de clave pero devuelven diferentes formas de datos, obtienes caché corrupto. Solución: usa matrices de clave únicas y descriptivas. -
Middleware y Edge Runtime no comparten cachés -- La Caché de datos es por región de despliegue. Middleware ejecutándose en el edge puede que no vea el mismo caché que una función de servidor Node.js. Solución: sé consciente de los límites de caché en despliegues multi-región.
Alternativas
| Alternativa | Usar cuando | No usar cuando |
|---|---|---|
React.cache() | Necesitas deduplicación por solicitud para llamadas que no son de fetch | Necesitas que los datos persistan entre solicitudes |
unstable_cache | Necesitas semántica de Caché de datos para llamadas de base de datos u ORM | Fetch simple con next.revalidate funciona |
| Redis o Memcached | Necesitas un caché compartido entre múltiples instancias de servidor | El caché integrado de Next.js es suficiente |
SWR stale-while-revalidate | Quieres almacenamiento en caché lado del cliente con revalidación automática | El almacenamiento en caché lado del servidor cubre tus necesidades |
cache: "no-store" en todas partes | Los datos deben ser siempre frescos (dashboards en tiempo real) | Puedes tolerar datos obsoletos para mejor rendimiento |
Preguntas frecuentes
¿Cuáles son las cuatro capas de almacenamiento en caché en Next.js y cómo se diferencian?
- Memoización de solicitudes: por renderizado, deduplica llamadas de fetch idénticas dentro de un único paso de renderizado de servidor
- Caché de datos: persiste entre solicitudes e implementaciones, indexado por URL de fetch y opciones
- Caché de ruta completa: almacena HTML prerrenderizado y carga de RSC para rutas estáticas en tiempo de compilación
- Caché del enrutador: caché en memoria lado del cliente para segmentos de ruta prefetcheados durante navegación
¿Cómo deduplicar consultas de base de datos que omiten la memoización automática de fetch?
import { cache } from "react";
import { db } from "@/lib/db";
export const getUser = cache(async (id: string) => {
return db.user.findUnique({ where: { id } });
});- Envuelve la función con
React.cache()para deduplicación por solicitud
¿Cuál es el comportamiento de caché predeterminado para fetch en Next.js 15+?
- El valor predeterminado es
cache: "auto", noforce-cachecomo en Next.js 14 - El framework decide el comportamiento de almacenamiento en caché basado en el contexto (funciones dinámicas, configuración de ruta)
- Sé explícito estableciendo
cacheonext.revalidateen cada fetch
¿Cuánto tiempo el Caché del enrutador mantiene las páginas en el lado del cliente?
- 30 segundos para páginas dinámicas
- 5 minutos para páginas estáticas
- Estos son valores predeterminados en Next.js 15+ y pueden configurarse vía
staleTimesennext.config.ts
¿Cómo optas por no usar almacenamiento en caché para una ruta?
export const dynamic = "force-dynamic";
export const fetchCache = "force-no-store";force-dynamicomite Full Route Cacheforce-no-storeomite Data Cache para todos los fetches en la ruta
¿Qué causa que Caché del enrutador sirva datos obsoletos después de una mutación?
- El Caché del enrutador lado del cliente no invalida automáticamente después de Server Actions
- La navegación atrás/adelante puede mostrar la página antigua
- Llama a
router.refresh()en el Client Component o reducestaleTimesen la configuración
¿Qué sucede si dos llamadas a unstable_cache comparten las mismas partes de clave pero devuelven diferentes datos?
- Obtienes caché corrupto -- la forma de datos incorrecta es devuelta
- Siempre usa matrices de clave únicas y descriptivas para cada llamada a
unstable_cache - Esta es una fuente común de errores de producción difíciles de depurar
¿Cómo preserva React.cache() los tipos de TypeScript?
import { cache } from "react";
const getUser = cache(
async (id: string): Promise<User | null> => {
return db.user.findUnique({ where: { id } });
}
);
// getUser: (id: string) => Promise<User | null>- La función envuelta retiene la misma firma exacta y tipo de retorno
¿Comparte Middleware la misma Caché de datos que las funciones de servidor Node.js?
- No. La Caché de datos es por región de despliegue
- Middleware ejecutándose en el edge puede que no vea el mismo caché que una función de servidor Node.js
- Sé consciente de los límites de caché en despliegues multi-región
¿Cuándo deberías usar unstable_cache versus React.cache()?
React.cache()proporciona solo deduplicación por solicitud; los datos no persisten entre solicitudesunstable_cacheproporciona semántica de Caché de datos (persiste entre solicitudes, soporta tags y revalidación)- Usa
unstable_cachepara llamadas de base de datos u ORM que necesiten almacenamiento en caché entre solicitudes
¿Cómo configuras la vida útil del Caché del enrutador para páginas dinámicas?
// next.config.ts
const config: NextConfig = {
experimental: {
staleTimes: {
dynamic: 0, // no cachear páginas dinámicas en el cliente
static: 300, // cachear páginas estáticas por 5 minutos
},
},
};¿Cuál es el tipo para la exportación de configuración de segmento fetchCache?
export const fetchCache:
| "auto"
| "default-cache"
| "only-cache"
| "force-cache"
| "force-no-store"
| "default-no-store"
| "only-no-store" = "auto";Relacionado
- Obteniendo datos -- Configurar opciones de caché en llamadas de fetch
- Revalidación -- Invalidar datos en caché
- Estático vs Dinámico -- Cómo el almacenamiento en caché determina el modo de renderizado
- Streaming -- Streaming de contenido en caché y dinámico