Saltearse al contenido

Modo snapshot

El modo snapshot permite que el cliente de la API de Content Island sirva todos sus métodos de lectura desde un fichero local en lugar de hacer una petición a la API en cada lectura. El contenido completo de un proyecto se exporta una vez como un único documento JSON — el snapshot (se transfiere comprimido con gzip desde el endpoint de exportación y se guarda como JSON plano en disco) — y el cliente lo lee comportándose exactamente igual que la API en vivo: los mismos métodos, las mismas estructuras y los mismos resultados.

El modo snapshot está pensado para aplicaciones que necesitan leer contenido con frecuencia sin llamar a la API en cada operación.

Exporta el proyecto una vez, guarda el snapshot en local y resuelve todas las lecturas directamente desde ese snapshot. Así reduces la latencia, eliminas tráfico innecesario a la API y tu aplicación aguanta mejor los problemas de red, los límites de peticiones o las caídas temporales de la API.

La generación de sitios estáticos encaja de forma natural, pero el mismo enfoque sirve también para SSR, ISR, server functions, trabajos en segundo plano y otras cargas en servidor.

Elegir un modo

El cliente opera en uno de estos dos modos:

ModoLas lecturas vienen dePor defecto
'api'La API REST en vivo de Content Island.
'snapshot'Un fichero snapshot local (snapshotPath).

Defines el modo al crear el cliente:

import { createClient } from '@content-island/api-client';
const client = createClient({
accessToken: 'YOUR_ACCESS_TOKEN',
mode: 'snapshot',
});

En modo 'snapshot' las lecturas se sirven desde el fichero snapshot local sin ninguna petición a la API y devolviendo exactamente los mismos resultados que la API en vivo. Cuando se omite mode, el cliente usa 'api' por defecto y se comporta exactamente como siempre.

El fichero snapshot

En modo snapshot el cliente lee de un fichero snapshot en disco. Lo indicas con la opción opcional snapshotPath:

const client = createClient({
accessToken: 'YOUR_ACCESS_TOKEN',
mode: 'snapshot',
snapshotPath: './content-island-snapshot.json',
});

snapshotPath es opcional y por defecto vale './content-island-snapshot.json'. No es obligatorio: omitirlo simplemente usa la ruta por defecto. Solo se lanza un error cuando el fichero en esa ruta está ausente, no se puede leer o no es válido — nunca por el mero hecho de no haber pasado la opción.

Carga remota de snapshots

La opción snapshotPath carga el snapshot desde un archivo del sistema local. Sin embargo, el snapshot también puede estar almacenado en una ubicación remota, como Amazon S3, Azure Blob Storage, un endpoint HTTP o una base de datos.

Para cargarlo desde una fuente externa, configura un snapshotLoader:

import { createClient } from '@content-island/api-client';
const client = createClient({
accessToken: 'YOUR_ACCESS_TOKEN',
mode: 'snapshot',
snapshotLoader: async () => {
const response = await fetch('https://storage.example.com/content-island-snapshot.json');
return response.text();
},
});

snapshotLoader es una función asíncrona definida por tu aplicación. El cliente la ejecuta cuando necesita cargar o actualizar el snapshot.

La función puede devolver:

  • una cadena con el JSON del snapshot;
  • un objeto ContentSnapshot ya parseado.

Si devuelve una cadena, el cliente se encarga de convertirla a JSON. En ambos casos, el contenido se valida antes de establecerlo como snapshot activo.

Opciones de carga inicial

El comportamiento de la primera lectura depende de cómo configures el cliente:

ConfiguraciónPrimera lecturaUso posterior de snapshotLoader
Solo snapshotLoaderejecuta el loader y carga el snapshot remotopuede volver a ejecutarse mediante refreshSnapshot()
snapshotPath y snapshotLoadercarga primero el snapshot del archivo localel loader se ejecuta al llamar a refreshSnapshot()

La combinación de snapshotPath y snapshotLoader es especialmente útil para servidores SSR y otros procesos de larga duración.

Permite iniciar la aplicación utilizando un snapshot incluido en el despliegue y cargar una versión más reciente después, sin realizar una petición remota durante el arranque ni reiniciar el proceso.

Actualización del snapshot activo

Llama a refreshSnapshot() para ejecutar de nuevo el snapshotLoader y comprobar si existe un snapshot más reciente:

const result = await client.refreshSnapshot();
// {
// status: 'updated' | 'unchanged',
// meta: { ... }
// }

El método devuelve uno de estos resultados:

statusSignificadoContenido de meta
'updated'El snapshot obtenido era más reciente y se ha establecido como activolos metadatos del nuevo snapshot
'unchanged'El snapshot obtenido era igual o más antiguo que el activo y se ha ignoradolos metadatos del snapshot que continúa activo

Un resultado 'unchanged' no es un error y no lanza ninguna excepción.

Este comportamiento evita que una respuesta retrasada o una actualización ejecutada fuera de orden sustituya contenido reciente por contenido antiguo.

Consulta la referencia de refreshSnapshot() para obtener información completa sobre su interfaz, resultados y tratamiento de errores.

Cuándo puede fallar una actualización

refreshSnapshot() lanza un ApiClientError y conserva el snapshot actual cuando no puede cargar o aceptar el nuevo contenido.

Puede ocurrir en los siguientes casos:

SituaciónMotivo
el snapshot tiene un projectId diferenteuna actualización no puede cambiar el proyecto del cliente
el snapshot tiene un view diferenteuna actualización no puede cambiar entre published y preview
el snapshotLoader fallano se ha podido obtener el snapshot remoto
el contenido no es JSON válidoel cliente no puede interpretarlo
la estructura no corresponde a un snapshot válidoel contenido no cumple el formato esperado
la versión del esquema no es compatibleel cliente no puede utilizar esa versión
el cliente utiliza el modo 'api'los clientes en modo API no mantienen un snapshot activo
no se ha configurado un snapshotLoaderno existe una fuente desde la que actualizar el snapshot

Si el loader lanza un error, el error original está disponible en error.cause.

Cuándo ejecutar la actualización

La librería no ejecuta actualizaciones periódicas automáticamente. Tu aplicación debe decidir cuándo llamar a refreshSnapshot().

Algunas estrategias habituales son:

  • Después de publicar contenido: un webhook del CMS puede notificar a tu aplicación para que actualice el snapshot inmediatamente.
  • De forma periódica: un temporizador puede comprobar cada cierto tiempo si existe una versión más reciente.
  • Manualmente: un endpoint interno o una acción de administración puede iniciar la actualización.

Cuando utilices un webhook, consulta la guía de webhooks de GitHub para ver un ejemplo de integración.

Lecturas y actualizaciones concurrentes

Una lectura realizada mientras refreshSnapshot() está en ejecución utiliza o bien el snapshot anterior completo, o bien el nuevo snapshot completo. Nunca utiliza un snapshot cargado parcialmente.

También es seguro llamar a refreshSnapshot() varias veces de forma concurrente. Las llamadas concurrentes comparten la misma operación de actualización y la instantánea válida más reciente es la que permanece activa.

Si un cliente configurado únicamente con un snapshotLoader todavía no ha cargado ningun snapshot, la primera llamada a refreshSnapshot() carga el snapshot inicial y lo establece como el snapshot activo.

Dónde se fija el modo

El modo se fija en dos sitios:

  • A nivel de cliente — la opción mode en createClient fija el valor por defecto para todas las lecturas.

  • Por lectura — los cinco métodos de lectura aceptan un mode dentro del objeto de consulta, que afecta solo a esa llamada:

    • getContentList
    • getContent
    • getRawContentList
    • getRawContent
    • getContentListSize
// El cliente lee de la API por defecto;
// esta única lectura se sirve desde el snapshot:
const client = createClient({
accessToken: 'YOUR_ACCESS_TOKEN',
snapshotPath: './content-island-snapshot.json',
});
const posts = await client.getContentList({
contentType: 'post',
mode: 'snapshot', // solo esta lectura
});

getProject sí funciona en modo snapshot: se sirve desde el objeto project del snapshot (idiomas incluidos). Simplemente no recibe objeto de consulta, así que no admite un mode por lectura: siempre sigue el modo a nivel de cliente. Para inspeccionar el snapshot en disco sin cargar el proyecto, usa getSnapshotInfo.

onRelatedContentMeta

onRelatedContentMeta es un callback opcional por consulta que informa de cómo fue la resolución del contenido relacionado. Se ejecuta en los mismos cinco métodos de lectura y tiene la firma:

onRelatedContentMeta: ({ resolvedDepth, partial }) => void;

Se invoca exactamente una vez por llamada:

  • resolvedDepth — hasta qué profundidad llegó realmente la resolución del contenido relacionado.
  • partialtrue cuando un límite de profundidad o de presupuesto de resolución dejó parte del grafo de contenido relacionado sin resolver.

El callback funciona en ambos modos y, para los mismos datos y la misma consulta, informa de valores idénticos:

  • En modo api, los valores provienen de las cabeceras de respuesta X-Related-Content-Resolved-Depth y X-Related-Content-Partial (la ausencia de la cabecera partial se interpreta como false).
  • En modo snapshot, los valores provienen de la resolución en anchura local sobre el snapshot.
const list = await client.getContentList({
contentType: 'post',
includeRelatedContent: 'all',
onRelatedContentMeta: ({ resolvedDepth, partial }) => {
console.log({ resolvedDepth, partial });
},
});

Cuando se omite onRelatedContentMeta, el comportamiento y las estructuras devueltas no cambian. Como mode, es una opción exclusiva del cliente y nunca se serializa en la cadena de consulta.

Las escrituras no están disponibles en modo snapshot

Un cliente en modo snapshot sirve solo lecturas. Un snapshot es una copia congelada y de solo lectura de tu contenido, así que no hay nada en lo que escribir. Todos los métodos de escritura y de gestión del esquema rechazan con un ApiClientError cuyo código es SNAPSHOT_MODE, y no realizan ninguna petición a la API — el rechazo ocurre localmente, antes de que se enviara petición alguna:

  • createContent
  • publishContent
  • updateContentFieldValue
  • uploadMedia
  • createModel
  • updateModel
  • deleteModel
  • createEnum
  • updateEnum
  • deleteEnum
import { createClient, ApiClientError } from '@content-island/api-client';
const client = createClient({
accessToken: 'YOUR_ACCESS_TOKEN',
mode: 'snapshot',
});
try {
await client.createContent(/* … */);
} catch (error) {
if (error instanceof ApiClientError && error.code === 'SNAPSHOT_MODE') {
// Las escrituras se rechazan en modo snapshot — no se envió ninguna petición.
}
}

Si necesitas leer de un snapshot y además escribir, crea un segundo cliente independiente en modo 'api' para las escrituras, autenticado con un Write Token:

import { createClient } from '@content-island/api-client';
// Lector: sirve las lecturas desde el snapshot local.
const reader = createClient({
accessToken: 'YOUR_READ_TOKEN',
mode: 'snapshot',
});
// Escritor: un cliente independiente en modo api para las escrituras.
const writer = createClient({
accessToken: 'YOUR_WRITE_TOKEN',
// mode usa 'api' por defecto
});
const posts = await reader.getContentList({ contentType: 'post' });
await writer.createContent(/* … */);

Exportar un snapshot desde la CLI

Genera un snapshot con el comando content-island export. Exporta el contenido de tu proyecto y lo escribe en disco:

Ventana de terminal
npx content-island export \
--access-token <your-read-token> \
--snapshot-path ./content-island-snapshot.json

--snapshot-path es opcional; si lo omites, el snapshot se escribe en ./content-island-snapshot.json, relativo al directorio de trabajo actual.

Para poder ejecutar este comando, previamente necesitas tener instalado el paquete de api cliente de content island npm i @content-island/api-client, o ejecutar directamente npx @content-island/api-client export --access-token <your-read-token>

El comando refleja la API programática exportSnapshot() uno a uno (la CLI está construida sobre ella). Sus flags son:

FlagDescripciónPor defecto
--access-tokenObligatorio. El token de acceso para la exportación. Si no, usa la variable de abajo.env CONTENT_ISLAND_ACCESS_TOKEN
--snapshot-pathDónde se escribe el fichero snapshot../content-island-snapshot.json
--domainSobrescribe el dominio de Content Island (para instancias self-hosted).
--secure-protocolUsa HTTPS explícitamente (es el valor por defecto).HTTPS
--no-secure-protocolUsa HTTP en lugar de HTTPS (instancias locales / self-hosted).
--api-versionSobrescribe la versión de la API usada para la exportación.

La exportación usa HTTPS por defecto. Pasa --no-secure-protocol solo para una instancia local o self-hosted servida sobre HTTP plano:

Ventana de terminal
npx content-island export \
--access-token <your-read-token> \
--domain localhost:3000 \
--no-secure-protocol

Si tiene éxito, el comando imprime un resumen — la ruta de salida, el tamaño del fichero, la marca de tiempo exportedAt y la vista — y termina con código 0.

Si falta el token, la petición falla, o el snapshot descargado no pasa la validación, el comando escribe el error en stderr (la salida de error estándar) y termina con un código de salida distinto de cero. Como escribe a través de un fichero temporal que se renombra a su ubicación final solo tras una descarga completa y validada, una ejecución fallida no deja ningún fichero parcial o inválido en --snapshot-path: o bien obtienes un snapshot completo, o se deja intacto el fichero anterior.

Un patrón habitual

Un montaje frecuente —depende del equipo y del proyecto— usa modo api en el desarrollo local y modo snapshot en las builds de producción:

  • Desarrollo local (mode: 'api') — contenido siempre fresco, sin un paso de exportación que recordar.
  • Build de producción (mode: 'snapshot') — rápido, reproducible y sin peticiones a la API por cada lectura.

Controla la elección con una variable de entorno para que el mismo código funcione en ambos casos:

import { createClient } from '@content-island/api-client';
const client = createClient({
accessToken: process.env.CONTENT_ISLAND_ACCESS_TOKEN,
mode: process.env.NODE_ENV === 'production' ? 'snapshot' : 'api',
});

En producción, genera el snapshot primero (un paso de exportación) y luego ejecuta tu build para que lea del fichero recién exportado.

GitHub Action

Este workflow exporta un snapshot en tiempo de build y luego construye tu sitio en modo snapshot:

name: Build
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 24
- run: npm ci
- name: Export content snapshot
env:
CONTENT_ISLAND_ACCESS_TOKEN: ${{ secrets.CONTENT_ISLAND_ACCESS_TOKEN }}
run: npx content-island export
- name: Build (snapshot mode)
env:
NODE_ENV: production
run: npm run build