Estrategias de almacenamiento en caché
Receta
SWR usa una caché en memoria por defecto. Personaliza el proveedor de caché para almacenamiento persistente, diseña tu estrategia de claves para maximizar los aciertos de caché y aprovecha la deduplicación para evitar solicitudes de red redundantes.
"use client";
import { SWRConfig } from "swr";
function App({ children }: { children: React.ReactNode }) {
return (
<SWRConfig
value={{
provider: () => new Map(), // Caché en memoria predeterminada
dedupingInterval: 2000, // Deduplica solicitudes dentro de 2s
}}
>
{children}
</SWRConfig>
);
}Ejemplo funcional
"use client";
import { SWRConfig, Cache } from "swr";
import useSWR from "swr";
// Caché persistente respaldada por localStorage
function localStorageProvider(): Cache {
const map = new Map<string, any>(
JSON.parse(localStorage.getItem("swr-cache") || "[]")
);
// Persiste en localStorage antes de descargar la página
window.addEventListener("beforeunload", () => {
const appCache = JSON.stringify(Array.from(map.entries()));
localStorage.setItem("swr-cache", appCache);
});
return {
get: (key: string) => map.get(key),
set: (key: string, value: any) => map.set(key, value),
delete: (key: string) => map.delete(key),
keys: () => map.keys(),
};
}
export function CachedApp({ children }: { children: React.ReactNode }) {
return (
<SWRConfig value={{ provider: localStorageProvider }}>
{children}
</SWRConfig>
);
}
// Los componentes se benefician automáticamente de la caché persistente
function UserProfile() {
const { data } = useSWR("/api/me", (url) => fetch(url).then((r) => r.json()));
return <div>{data?.name}</div>;
}Profundización
Cómo funciona
- La caché de SWR es un almacén clave-valor simple. La clave de
useSWR(key)se asigna directamente a una entrada de caché. - La caché predeterminada es una instancia de
Mapque vive en memoria y se reinicia al recargar la página. - Deduplicación: cuando varios componentes solicitan la misma clave dentro de
dedupingIntervalms, solo se ejecuta un fetch. Todos los hooks reciben la misma promesa. - Las entradas de caché almacenan datos, error y metadatos (marcas de tiempo). SWR los usa para decidir si los datos están obsoletos.
- La función
providerenSWRConfigse invoca una vez al montar y debe devolver un objeto compatible conCache.
Variaciones
Diseño de claves para eficiencia de caché:
// Bien: claves estables y serializables
useSWR(`/api/users/${id}`, fetcher);
useSWR(["/api/users", id, "posts"], fetcher);
// Mal: referencias de objetos inestables (no serializables)
useSWR({ url: "/api/users", id }, fetcher); // No cacheará correctamentePrecarga en caché:
import { preload } from "swr";
const fetcher = (url: string) => fetch(url).then((r) => r.json());
// Precarga al pasar el cursor o en prefetch de ruta
function ProductCard({ id }: { id: string }) {
const handleMouseEnter = () => {
preload(`/api/products/${id}`, fetcher);
};
return (
<a href={`/products/${id}`} onMouseEnter={handleMouseEnter}>
Ver producto
</a>
);
}Vaciar la caché:
import { useSWRConfig } from "swr";
function LogoutButton() {
const { cache } = useSWRConfig();
const handleLogout = () => {
// Borra todos los datos en caché al cerrar sesión
if (cache instanceof Map) {
cache.clear();
}
};
return <button onClick={handleLogout}>Cerrar sesión</button>;
}Caché con TTL:
function ttlProvider(): Cache {
const cache = new Map<string, { value: any; expiry: number }>();
const TTL = 5 * 60 * 1000; // 5 minutos
return {
get: (key: string) => {
const entry = cache.get(key);
if (!entry) return undefined;
if (Date.now() > entry.expiry) {
cache.delete(key);
return undefined;
}
return entry.value;
},
set: (key: string, value: any) => {
cache.set(key, { value, expiry: Date.now() + TTL });
},
delete: (key: string) => cache.delete(key),
keys: () => cache.keys(),
};
}Notas de TypeScript
- Importa
Cachedesdeswrpara tipar proveedores personalizados. - La opción
providerespera() => Cache.
import type { Cache } from "swr";
const provider = (): Cache => {
const map = new Map();
return {
get: (key: string) => map.get(key),
set: (key: string, value: any) => map.set(key, value),
delete: (key: string) => map.delete(key),
keys: () => map.keys(),
};
};Errores comunes
- La caché en memoria predeterminada se pierde al recargar la página. Si necesitas persistencia, implementa un proveedor personalizado.
dedupingIntervalsolo deduplica solicitudes concurrentes. No evita volver a obtener datos después de que expire el intervalo.- Las claves de array como
["/api/users", id]se serializan para el almacenamiento en caché. El orden de los elementos importa:[a, b]y[b, a]son claves de caché diferentes. - Los proveedores de caché personalizados deben implementar la interfaz
Cachecompleta, incluidokeys(). Los métodos faltantes provocarán errores en tiempo de ejecución. localStoragetiene un límite de ~5MB. Las cachés grandes pueden fallar en silencio. Implementa manejo de errores en el métodosetde tu proveedor.- No compartas el estado de la caché entre proveedores
SWRConfigen distintos niveles de anidación; cada proveedor crea su propio ámbito.
Alternativas
| Enfoque | Ventajas | Desventajas |
|---|---|---|
| Caché Map predeterminada | Cero configuración, rápida | Se pierde al recargar |
| Proveedor de localStorage | Persiste entre sesiones | Límite de 5MB, la API síncrona bloquea el hilo principal |
| Proveedor de IndexedDB | Gran almacenamiento, asíncrono | Implementación más compleja |
| Sin caché (dedupingInterval: 0) | Datos siempre frescos | Más solicitudes de red |
FAQs
¿Cuál es la implementación de caché predeterminada en SWR?
SWR usa una instancia de Map en memoria por defecto. Se reinicia al recargar la página. No se necesita configuración para el almacenamiento en caché básico.
¿Cómo hago que la caché de SWR persista entre recargas de página?
Implementa un proveedor de caché personalizado respaldado por localStorage o IndexedDB. El proveedor debe implementar la interfaz Cache (get, set, delete, keys). Persiste en beforeunload y rehidrata al inicializar.
¿Cuál es la diferencia entre deduplicación y almacenamiento en caché en SWR?
- La deduplicación (
dedupingInterval) evita solicitudes duplicadas concurrentes dentro de una ventana de tiempo. - El almacenamiento en caché guarda datos durante toda la sesión para que los renderizados posteriores usen valores en caché.
- La deduplicación no evita volver a obtener datos después de que expire el intervalo.
Trampa: ¿Importa el orden de los elementos en las claves de array para el almacenamiento en caché?
Sí. ["/api/users", id] y [id, "/api/users"] son claves de caché diferentes. SWR serializa los elementos del array en orden, por lo que cambiar el orden crea una entrada de caché distinta.
¿Cómo precargo datos en la caché de SWR?
import { preload } from "swr";
const fetcher = (url: string) => fetch(url).then((r) => r.json());
// Precarga al pasar el cursor
const handleMouseEnter = () => {
preload(`/api/products/${id}`, fetcher);
};¿Cómo vacío toda la caché de SWR (p. ej., al cerrar sesión)?
const { cache } = useSWRConfig();
const handleLogout = () => {
if (cache instanceof Map) {
cache.clear();
}
};¿Puedo usar claves de objeto para el almacenamiento en caché de SWR?
No. Las referencias de objetos no son serializables por SWR. Usa strings o arrays como claves. useSWR({ url: "/api/users", id }, fetcher) no cacheará correctamente.
Trampa: ¿Qué ocurre si localStorage supera su límite de ~5MB en un proveedor personalizado?
La escritura falla en silencio. Implementa manejo de errores en el método set de tu proveedor usando try/catch, y considera expulsar entradas antiguas o recurrir al almacenamiento en memoria.
¿Cómo tipar un proveedor de caché personalizado en TypeScript?
import type { Cache } from "swr";
const provider = (): Cache => {
const map = new Map();
return {
get: (key: string) => map.get(key),
set: (key: string, value: any) => map.set(key, value),
delete: (key: string) => map.delete(key),
keys: () => map.keys(),
};
};¿Qué tipo de TypeScript debe tener la opción provider en SWRConfig?
La opción provider espera una función de tipo () => Cache. Importa Cache desde swr para tipar tu implementación personalizada. Los métodos faltantes de la interfaz (keys, delete) provocarán errores en tiempo de ejecución.
¿Cómo funciona un proveedor de caché basado en TTL con SWR?
Almacena entradas con una marca de tiempo de expiración. En get, comprueba si la entrada ha expirado y devuelve undefined en ese caso. Esto obliga a SWR a tratarlo como un fallo de caché y volver a obtener los datos.