Implementación de caché de Redis para aplicaciones de Next.js 15
Implementación de caché de Redis para aplicaciones de Next.js 15
Next.js 15 introduce estrategias de almacenamiento en caché agresivas de forma predeterminada, pero depender únicamente de la caché integrada en el sistema de archivos o en memoria para aplicaciones empresariales es una fórmula para obtener datos desactualizados, rendimiento impredecible y dolores de cabeza en el despliegue. Cuando escala a través de múltiples funciones serverless o contenedores, el almacenamiento en caché local deja de funcionar. Necesita una caché distribuida. Necesita Redis.
Esta guía cubre la implementación precisa del almacenamiento en caché de Redis para aplicaciones de Next.js 15. Construiremos una arquitectura robusta y escalable utilizando Upstash Redis (o cualquier proveedor de Redis) y la API nativa unstable_cache de Next.js, eludiendo los valores predeterminados frágiles y tomando el control total de su capa de datos.
El problema
Next.js 15 depende en gran medida de la caché de la Fetch API y de la caché de los segmentos de ruta. Por defecto, almacena los datos almacenados en caché en el sistema de archivos. Esto funciona bien para un sitio estático desplegado en una sola instancia de Node.js.
Pero cuando despliega en plataformas serverless como Vercel o AWS Lambda, su aplicación se escala levantando múltiples instancias independientes. La Instancia A no tiene acceso a la caché del sistema de archivos de la Instancia B. Si un usuario realiza una petición a la Instancia A, podría obtener datos actualizados. Si cae en la Instancia B, podría obtener datos obsoletos o desencadenar una consulta redundante a la base de datos.
Además, el almacenamiento en caché del sistema de archivos es volátil. El despliegue de una nueva versión de su aplicación a menudo elimina toda la caché, lo que provoca picos masivos de tráfico en su base de datos inmediatamente después de un despliegue (avalancha de caché o cache stampede).
Necesitamos una caché distribuida y persistente que se ubique fuera del código de la aplicación y sobreviva a los despliegues.
Por qué es difícil
Integrar Redis en Next.js no es solo cuestión de llamar a redis.get() y redis.set(). Next.js 15 tiene una arquitectura basada en React Server Components (RSC) muy estructurada. El framework quiere ser el propietario de la capa de almacenamiento en caché.
Si simplemente envuelve sus llamadas a la base de datos en comandos de Redis, estará luchando contra el framework. Perderá los beneficios de la revalidación bajo demanda (revalidateTag, revalidatePath) y correrá el riesgo de servir datos inconsistentes al cliente.
El desafío radica en inyectar Redis dentro del ciclo de vida de almacenamiento en caché de Next.js. Debemos interceptar las lecturas y escrituras de caché del framework, reemplazando sin problemas la caché volátil del sistema de archivos con nuestro almacén distribuido de Redis, manteniendo al mismo tiempo la compatibilidad con las primitivas de revalidación de Next.js.
Esto requiere controladores de caché personalizados (custom cache handlers), una característica que ha estado en varios estados de soporte experimental en Next.js. En Next.js 15, configurar un custom cache handler es el único camino aceptable para aplicaciones de alto tráfico.
Arquitectura
Nuestra arquitectura consta de tres capas:
- La capa de aplicación (Next.js 15): React Server Components que ejecutan la lógica de negocio y renderizan la UI.
- El interceptor de caché: Un controlador de caché personalizado de Next.js que intercepta las solicitudes de
unstable_cacheyfetch. - La capa de caché distribuida (Redis): Un almacén rápido de clave-valor en memoria que contiene las respuestas serializadas.
Cuando un Server Component solicita datos:
- El framework llama a nuestro controlador de caché personalizado.
- El controlador busca la clave en Redis.
- Si ocurre un acierto de caché (cache hit), deserializamos el JSON y lo devolvemos. El framework renderiza la UI inmediatamente.
- Si ocurre un fallo de caché (cache miss), el framework ejecuta la lógica de obtención de datos (por ejemplo, consultando PostgreSQL) y pasa el resultado a nuestro controlador, el cual lo escribe en Redis antes de devolverlo al componente.
Esto garantiza que todas las instancias serverless compartan una única fuente de verdad.
Implementación
Utilizaremos el paquete @neshca/cache-handler. Este proporciona una base sólida y lista para producción para controladores de caché personalizados de Next.js, diseñada específicamente para conectar Redis. Utilizaremos ioredis como nuestro cliente de Redis.
1. Instalar dependencias
npm install @neshca/cache-handler ioredis
2. Configurar el cliente de Redis
Cree una instancia robusta del cliente de Redis. No inicialice múltiples conexiones por cada invocación serverless.
// lib/redis.ts
import { Redis } from 'ioredis';
const redisUrl = process.env.REDIS_URL;
if (!redisUrl) {
throw new Error('REDIS_URL environment variable is not defined');
}
// Asegurar una única instancia en desarrollo para evitar fugas de conexión durante HMR
const globalForRedis = global as unknown as { redis: Redis };
export const redis = globalForRedis.redis || new Redis(redisUrl, {
maxRetriesPerRequest: 3,
enableReadyCheck: false,
});
if (process.env.NODE_ENV !== 'production') globalForRedis.redis = redis;
3. Crear el controlador de caché (Cache Handler)
Este archivo le indica a Next.js cómo comunicarse con Redis. Mapea las operaciones de caché de Next.js (get, set, revalidateTag) a comandos de Redis.
// cache-handler.mjs
import { CacheHandler } from '@neshca/cache-handler';
import createRedisHandler from '@neshca/cache-handler/redis-strings';
import { Redis } from 'ioredis';
CacheHandler.onCreation(async () => {
let client;
try {
// Instanciamos el cliente aquí.
// En producción, asegúrese de tener configurado REDIS_URL.
client = new Redis(process.env.REDIS_URL, {
maxRetriesPerRequest: 3,
lazyConnect: true, // No bloquear el arranque
});
// Probar la conexión
client.on('error', (error) => {
console.error('Redis connection error:', error);
});
} catch (error) {
console.warn('Failed to initialize Redis client for cache handler', error);
}
return {
handlers: [
createRedisHandler({
client,
keyPrefix: 'next-cache:',
timeoutMs: 1000, // Fallar rápido si Redis es lento
}),
],
};
});
export default CacheHandler;
4. Conectarlo a Next.js
Indique a Next.js que utilice su controlador de caché personalizado en next.config.js.
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
// Nota: Las funciones experimentales pueden cambiar, pero este es el patrón actual para Next 15
cacheHandler: require.resolve('./cache-handler.mjs'),
cacheLife: {
default: {
stale: 3600, // 1 hora
revalidate: 86400, // 1 día
},
},
},
};
module.exports = nextConfig;
5. Obtención de datos
Ahora, utilice unstable_cache o fetch nativo como lo haría normalmente. El framework enrutará de forma transparente el almacenamiento en caché a través de Redis.
// app/products/[id]/page.tsx
import { unstable_cache } from 'next/cache';
import { db } from '@/lib/db';
const getProduct = unstable_cache(
async (id: string) => {
console.log('Cache miss: Fetching product from DB', id);
return await db.product.findUnique({ where: { id } });
},
['product-details'], // Segmentos de clave de caché
{ tags: ['products'], revalidate: 3600 }
);
export default async function ProductPage({ params }: { params: { id: string } }) {
const product = await getProduct(params.id);
if (!product) return <div>Product not found</div>;
return (
<main>
<h1>{product.name}</h1>
<p>{product.description}</p>
</main>
);
}
Cuando necesite invalidar estos datos (por ejemplo, mediante un webhook en un panel de administración), simplemente llame a revalidateTag('products'). El controlador de caché traducirá esto en un comando DEL de Redis o en una invalidación basada en etiquetas, asegurando que la próxima solicitud consulte la base de datos.
Errores comunes (Pitfalls)
Implementar el almacenamiento en caché de Redis en Next.js 15 le expone a varias trampas arquitectónicas. Evite estos modos de fallo:
1. Latencia de Redis y tormentas de tiempo de espera (Timeout Storms)
Redis es rápido, pero la latencia de red es impredecible. Si su instancia de Redis se cae o la latencia de conexión se dispara a 2000ms, toda su aplicación de Next.js se colgará esperando la caché.
Solución: Establezca límites de tiempo estrictos en su controlador de caché (por ejemplo, timeoutMs: 500). Si Redis no responde en 500ms, el controlador de caché debe fallar de manera controlada, tratándolo como un fallo de caché y consultando directamente a la base de datos. La disponibilidad es más importante que el rendimiento.
2. Serialización de objetos grandes
Redis almacena cadenas de texto. Next.js serializa sus datos a JSON antes de enviarlos al controlador de caché. Almacenar bloques JSON masivos de 5MB que representen una tabla completa sin paginar saturará la E/S de red de su Redis y consumirá ciclos de CPU de serialización/deserialización.
Solución: Guarde en caché solo lo que necesite. Proyecte sus consultas de base de datos. No haga SELECT *. Recupere únicamente los campos requeridos por el componente, reduciendo el tamaño del payload.
3. Aludes de caché (Cache Stampedes)
Cuando una clave de caché popular expira o se invalida, cientos de solicitudes concurrentes pueden golpear el servidor al mismo tiempo. Todas experimentarán un fallo de caché y saturarán la base de datos con la misma consulta, pudiendo tumbar su BD principal.
Solución: Next.js unstable_cache proporciona de forma inherente deduplicación de solicitudes por instancia. Para una deduplicación distribuida, necesita un mecanismo de bloqueo respaldado por Redis o confiar en patrones de stale-while-revalidate (SWR) para que el usuario reciba datos obsoletos mientras ocurre la revalidación en segundo plano.
4. Fugas de conexión en entornos Serverless
Los entornos serverless (como las funciones de Vercel) congelan y reanudan los contextos de ejecución. Si abre una nueva conexión de Redis en cada solicitud, agotará el límite de conexiones de su Redis de inmediato.
Solución: Instancie el cliente de Redis fuera del alcance del manejador de solicitudes. Utilice clientes de Redis basados en HTTP (como la API REST de Upstash) si no puede mantener conexiones TCP persistentes, aunque @neshca/cache-handler gestiona correctamente los clientes persistentes si se configuran adecuadamente.
Resultado
Al implementar un controlador de caché de Redis personalizado, elimina la caché impredecible y efímera del sistema de archivos y la reemplaza por un almacén de datos centralizado y robusto.
Sus despliegues ya no destruyen la caché. Sus instancias serverless comparten una única fuente de verdad. La carga de su base de datos disminuye drásticamente y sus tiempos de respuesta se estabilizan en todo el clúster.
Next.js 15 quiere ser el propietario del almacenamiento en caché. Permítale gestionar la API, pero usted debe gestionar la infraestructura. Redis le proporciona el control necesario para operar Next.js a escala. No se conforme con las opciones por defecto.
Servicio de Seven Labs
Desarrollo de SaaS - Next.js y MERN
