Cache de páginas dinámicas en el borde: Next.js 16 + Server Actions + Cloudflare Workers + Redis
El problema que ve tu agente
Tu agente de IA acaba de terminar una corrida de pruebas y te avisa: Tests passed. El siguiente paso es abrir la página de resultados en el navegador. La página está construida con Next.js 16 y muestra datos que dependen de la última ejecución del modelo, por lo que la lógica está dentro de una Server Action. Cuando el agente abre la URL, el tiempo de respuesta supera los 2 segundos y la experiencia se vuelve frustrante. El agente no necesita saber que la latencia proviene de una llamada a la base de datos o de un cálculo pesado; solo necesita que la página se entregue rápido.
Regla de oro: cachea la salida de la Server Action en el borde y usa Redis como fuente de verdad
Si tu agente siempre solicita la misma recurso dinámico dentro de un intervalo corto, la forma más sencilla de acelerar la entrega es almacenar el resultado de la Server Action en el borde (Edge) y reutilizarlo mientras sea válido. Redis actúa como la única fuente de verdad para la caché: el Worker escribe el valor en Redis la primera vez y luego lo lee en cada petición posterior. Cuando el TTL expira, el Worker vuelve a ejecutar la Server Action, actualiza Redis y sirve la nueva versión.
Arquitectura mínima
El esquema que permite cumplir la regla anterior consta de cuatro componentes:
- Next.js 16: framework que expone la Server Action y permite el rendering híbrido.
- Server Actions: funciones que se ejecutan en el servidor y pueden devolver HTML, JSON o cualquier tipo de payload.
- Cloudflare Workers: capa de borde que intercepta la solicitud, busca en Redis y, si es necesario, invoca la Server Action.
- Redis (Cloudflare KV o Redis Enterprise): almacén rápido de pares clave‑valor donde se guarda la respuesta serializada.
Next.js 16 y Server Actions
En Next.js 16 una Server Action se declara como una función exportada desde app/api. La función puede recibir parámetros y devolver cualquier objeto serializable. Lo importante es que la acción se ejecuta solo en el servidor, por lo que no expone lógica sensible al cliente.
Cloudflare Workers como capa de borde
Los Workers se despliegan en la red de Cloudflare, cerca del usuario final. Cada petición pasa primero por el Worker, que decide si sirve un contenido en caché o delega la ejecución a la aplicación Next.js. Gracias a la API de fetch integrada, el Worker puede llamar a la ruta de la Server Action como si fuera un endpoint HTTP.
Redis como store de caché
Redis ofrece operaciones atómicas (GET, SETEX, DEL) y tiempos de vida (TTL) precisos. En el contexto de Cloudflare, puedes usar Workers KV para datos menos volátiles o un Redis externo (por ejemplo, Redis Enterprise) para latencias sub‑milisegundo y expiración granular.
Implementación paso a paso
1. Definir la Server Action
En app/api/report/route.js crea la acción que genera el reporte. En este ejemplo simulamos una carga pesada con await new Promise(r => setTimeout(r, 800)).
import { json } from 'next/server';
export async function POST(request) {
const { runId } = await request.json();
// Simulación de cálculo intensivo
await new Promise(resolve => setTimeout(resolve, 800));
const result = {
runId,
status: 'completed',
metrics: {
accuracy: 0.94,
latency: 123,
},
timestamp: Date.now(),
};
return json(result);
}
2. Exportar la acción a un endpoint Edge
El Worker intercepta /api/report. Si el valor está en Redis, lo devuelve directamente; si no, llama a la Server Action y almacena la respuesta.
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});
async function handleRequest(request) {
const url = new URL(request.url);
if (url.pathname !== '/api/report') return fetch(request);
const cacheKey = `report:${url.searchParams.get('runId')}`;
const cached = await REDIS.get(cacheKey);
if (cached) {
return new Response(cached, {
headers: { 'Content-Type': 'application/json', 'X-Cache': 'HIT' },
});
}
// Forward to Next.js Server Action
const upstream = `https://my-next-app.com/api/report`;
const upstreamResp = await fetch(upstream, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ runId: url.searchParams.get('runId') }),
});
const body = await upstreamResp.text();
// Store with TTL de 60 s
await REDIS.setex(cacheKey, 60, body);
return new Response(body, {
headers: { 'Content-Type': 'application/json', 'X-Cache': 'MISS' },
});
}
3. Integrar Redis en el Worker
Cloudflare Workers no incluyen un cliente Redis nativo, pero puedes usar la librería @upstash/redis (compatible con Workers) o cualquier cliente HTTP‑compatible. Aquí usamos Upstash como ejemplo.
import { Redis } from '@upstash/redis';
const REDIS = new Redis({
url: 'https://',
token: '',
});
Coloca esta inicialización al inicio del script del Worker. Si prefieres Workers KV, sustituye REDIS.get y REDIS.setex por KV.get y KV.put con la opción { expirationTtl: 60 }.
4. Usar la caché desde el cliente
En el componente React que muestra el reporte, llama al endpoint del Worker. El agente sólo ve una llamada fetch y un JSON rápido.
'use client';
import { useEffect, useState } from 'react';
export default function Report({ runId }) {
const [data, setData] = useState(null);
const [cacheStatus, setCacheStatus] = useState('...');
useEffect(() => {
fetch(`/api/report?runId=${runId}`)
.then(res => {
setCacheStatus(res.headers.get('X-Cache') || 'UNKNOWN');
return res.json();
})
.then(setData);
}, [runId]);
if (!data) return Cargando reporte...
;
return (
<div>
<p>Cache: {cacheStatus}</p>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
}
Control de expiración y revalidación
El TTL de 60 segundos funciona bien para pruebas que se ejecutan con frecuencia, pero en producción querrás combinarlo con Incremental Static Regeneration (ISR) para que la página estática se actualice sin bloquear al agente.
TTL y revalidación manual
Cuando el agente necesita datos frescos antes de que expire el TTL, puedes forzar la invalidación mediante una petición a un endpoint de purge.
// Worker endpoint /api/purge-report
if (url.pathname === '/api/purge-report' && request.method === 'POST') {
const { runId } = await request.json();
const key = `report:${runId}`;
await REDIS.del(key);
return new Response('Purged', { status: 200 });
}
El agente simplemente llama a /api/purge-report después de una nueva ejecución del modelo, garantizando que la siguiente petición obtenga la versión más reciente.
Ventajas concretas para tu agente
- Respuesta sub‑segundo: la mayoría de las peticiones son servidas desde Redis en el borde, eliminando la latencia de la base de datos.
- Consistencia: al usar Redis como única fuente, el agente nunca recibe versiones divergentes de los datos.
- Escalabilidad automática: los Workers escalan a nivel de solicitud sin necesidad de orquestadores ni colas.
- Control granular: TTL y purge permiten al agente decidir cuándo necesita frescura versus velocidad.
- Costos predecibles: la carga al origen (Next.js) se reduce drásticamente, lo que baja el consumo de recursos de cómputo.
Consideraciones de seguridad y límites
- Validación de parámetros: nunca confíes en
runIdsin sanitizar; usa un whitelist o firma HMAC para evitar ataques de enumeración. - Rate limiting en el Worker: Cloudflare permite establecer límites por IP o token; protege la API de abusos inesperados.
- Encriptado en tránsito: tanto la llamada al Worker como la conexión a Redis deben usar HTTPS/TLS.
- Tamaño de la caché: Redis tiene un límite de memoria configurable; define políticas de evicción (LRU) acorde al volumen de reportes.
- Persistencia: si los datos son críticos, habilita snapshots o replicación en Redis para evitar pérdida tras reinicios.
Conclusión práctica
Tu agente ya no tiene que esperar a que una Server Action se ejecute en el origen. Al colocar un Worker en el borde que consulta Redis antes de delegar, conviertes una página dinámica en un recurso casi estático sin perder la capacidad de actualizarla bajo demanda. La regla de oro —cachear la salida de la Server Action en el borde y usar Redis como fuente de verdad— se traduce en tiempos de respuesta de menos de 200 ms, menos carga en tu aplicación Next.js y una arquitectura que sigue siendo simple de mantener.
En la próxima entrega exploraremos cómo combinar esta estrategia con WebSockets en Workers para notificar al agente en tiempo real cuando una caché se invalida. Hasta entonces, prueba el flujo descrito y mide la diferencia en tu dashboard de métricas.