Dynamic Snapshot en Vercel con Redis y Custom HTTP Webhooks

Intro

Hace unas semanas os contamos cómo funciona el modo Snapshot en Content Island y cómo convertirlo en Dynamic Snapshot: este modo te permite trabajar en modo SSR con un rendimiento cercano a SSG: el contenido de tu proyecto en Content Island lo tienes disponible en formato JSON en tu servidor y puedes lanzar las consultas directamente en memoria; además, cada vez que se publican cambios en el contenido, se puede actualizar cada instancia de tu entorno servidor de forma automática.

En esos artículos vimos el patrón general. Ahora vamos a llevarlo a un caso real: una aplicación desplegada en Vercel que utiliza Redis para compartir el snapshot entre sus Functions y un Custom HTTP Webhook para actualizarlo al publicar contenido.

El ejemplo completo (una web de coches con TanStack Start, Redis y Vercel) está en este repositorio, con dos guías paso a paso que enlazamos al final.

La configuración sigue siendo la misma que vimos en el artículo anterior:

export const contentIslandClient = createClient({
  accessToken,
  mode: "snapshot",
  snapshotLoader: loadSnapshotJson,
});

snapshotLoader indica de dónde debe cargar el JSON y refreshSnapshot() permite volver a cargarlo cuando hay contenido nuevo. El resto de la aplicación no cambia: puedes seguir utilizando las mismas llamadas a la API, los mismos componentes, rutas y consultas.

El problema: en serverless la memoria no es la fuente de verdad

En un servidor node tradicional esto es bastante sencillo. El proceso se mantiene en ejecución, carga el snapshot al arrancar y lo conserva en memoria. Cuando publicas algún cambio, recibe el aviso y vuelve a cargar el JSON. Si tienes varias instancias, puedes notificarlas mediante Redis Pub/Sub para que todas se actualicen.

En Vercel el escenario es distinto. Las Functions se crean y reutilizan en función del tráfico, pueden quedar inactivas y desaparecer en cualquier momento. Además, cada instancia mantiene su propia memoria. Redis Pub/Sub no resuelve del todo el problema: si una Function está inactiva cuando se publica el aviso, no lo recibe y conserva una versión antigua del contenido.

Por eso, en este escenario la memoria solo puede utilizarse como caché:

La memoria es el lugar más rápido desde el que servir el contenido, pero no puede ser la fuente de verdad.

La solución es que cada instancia compruebe periódicamente en Redis si hay una versión nueva. Esta comprobación es muy ligera, ya que solo necesita consultar el número de versión del snapshot.

El reparto de responsabilidades queda así:

Pieza Responsabilidad
Redis Almacena el snapshot vigente y su número de versión
Memoria de la Function Mantiene una copia local para resolver las consultas
Content Island Al publicar contenido, llama mediante un webhook al endpoint de refresco
Endpoint de refresco Descarga el nuevo snapshot desde Content Island y lo guarda en Redis

Cómo queda el flujo

El flujo tiene dos partes: la actualización del snapshot en Redis y la comprobación que hace cada instancia antes de responder.

  1. Al publicar contenido en Content Island, el Custom HTTP Webhook llama al endpoint de refresco de la aplicación.

  2. El endpoint descarga el snapshot mediante exportSnapshot(), lo comprime y lo guarda en Redis junto con su número de versión.

  3. En cada petición, un middleware comprueba cuánto tiempo ha pasado desde la última consulta a Redis.

  4. Si todavía no ha transcurrido el intervalo configurado, continúa utilizando el snapshot que ya tiene en memoria, sin consultar Redis.

  5. Cuando se cumple el intervalo, consulta únicamente el campo version de Redis. Si no ha cambiado, continúa utilizando la copia en memoria.

  6. Si encuentra una versión nueva, refreshSnapshot() descarga el snapshot desde Redis y sustituye la copia que hay en memoria. Esa misma petición ya se responde con el contenido actualizado.

Flujo de Dynamic Snapshot

Para que esta comprobación tenga un impacto mínimo en el rendimiento, no se consulta Redis en cada petición. El intervalo se configura mediante SNAPSHOT_CHECK_INTERVAL_MS; por ejemplo, podemos establecerlo en cinco minutos en producción.

Cuando toca realizar la comprobación, solo se lee el campo version, que contiene un timestamp y ocupa apenas unas decenas de bytes. El snapshot completo únicamente se descarga cuando esa versión ha cambiado.

Además, la versión y el snapshot comprimido se guardan en un mismo hash de Redis mediante un HSET atómico. De esta forma, una instancia nunca puede leer la versión nueva junto con los datos del snapshot anterior.

Este diseño introduce una pequeña ventana de consistencia: una instancia podría servir la versión anterior hasta que vuelva a consultar Redis. Con un intervalo de cinco minutos, el contenido tardará como máximo ese tiempo en actualizarse en una instancia que siga recibiendo peticiones. A cambio, la consulta a Redis se realiza como mucho una vez cada cinco minutos por instancia, por lo que el impacto sobre cada respuesta es prácticamente nulo (esta ventana de tiempo es configurable).

Quién avisa: el Custom HTTP Webhook

Ya tenemos un endpoint que actualiza el snapshot en Redis y un mecanismo para que cada instancia detecte cuándo hay una versión nueva. El siguiente paso es llamar automáticamente a ese endpoint cada vez que se publica contenido.

Content Island ofrece varias formas de automatizar este proceso. Por ejemplo, puedes integrarlo en un workflow de GitHub Actions. En Vercel, sin embargo, podemos hacerlo de una forma mucho más directa: configurar el endpoint de refresco como un Custom HTTP Webhook.

Content Island llama directamente a la URL que le digas, con las cabeceras que tú le pongas.

Se configura en la sección Webhooks del proyecto, pulsando Add Webhook y eligiendo Custom HTTP Webhook.

Crear un Custom HTTP Webhook

El formulario es corto y tiene dos partes:

  • Arriba, un nombre para identificarlo y la URL de tu endpoint de refresco, algo como https://tu-proyecto.vercel.app/api/snapshot/refresh.
  • Abajo, la sección Headers, donde añades uno llamado x-refresh-secret con el mismo valor que tengas en SNAPSHOT_REFRESH_SECRET en tu despliegue.

Configuración del Custom HTTP Webhook

Ese header es lo único que separa tu endpoint del primero que dé con la URL, así que conviene validarlo antes de tocar nada.

if (!isValidSecret(request.headers.get("x-refresh-secret"))) {
  return Response.json({ error: "Unauthorized" }, { status: 401 });
}

Para ver si ha quedado bien configurado, tienes el botón Send Test, que manda la petición en ese momento y te deja mirar la respuesta en los logs de tu proveedor.

{
  "status": "updated",
  "version": "2026-08-23T14:19:01.116Z",
  "encoding": "gzip",
  "compressedSize": 104292,
  "uncompressedSize": 408067
}

Botón para enviar test

Y luego queda hacer la prueba de verdad: cambiar cualquier contenido en Content y publicarlo. Content Island llama al webhook, el endpoint guarda la nueva versión en Redis y cada instancia se actualiza, como máximo, dentro del tiempo que hayas configurado en SNAPSHOT_CHECK_INTERVAL_MS.

Las guías

En este artículo hemos visto cómo funciona la solución completa. Si quieres implementarla, en el repositorio tienes dos guías con todos los pasos, capturas y comandos necesarios:

Conclusión

Dynamic Snapshot está pensado para aplicaciones SSR que necesitan servir contenido dinámico. Las consultas se resuelven directamente desde memoria, sin llamar a la API de Content Island en cada petición, y el contenido se actualiza al publicar sin reconstruir ni volver a desplegar el sitio.

En Vercel, Redis mantiene el snapshot vigente y cada Function conserva una copia en memoria. Cuando detecta una versión nueva, actualiza esa copia y continúa resolviendo las consultas localmente.

El Custom HTTP Webhook completa el proceso: publicas en Content Island, el webhook llama al endpoint de refresco y el nuevo contenido queda disponible automáticamente para todas las instancias.

Todo este montaje tiene un objetivo: conseguir en SSR un rendimiento cercano a SSG sin renunciar al contenido dinámico.