Bucket storage (S3)
Un Dynamic Snapshot sirve todo el contenido desde la memoria del servidor y lo refresca en caliente cuando publicas.
Sin rebuild. Sin una llamada al API por lectura.
El patrón tiene dos piezas:
- De dónde se lee el snapshot → el
snapshotLoader. - Quién lanza el refresco →
refreshSnapshot().
En esta versión el JSON vive en un bucket S3 (MinIO en local).
Un script de ejemplo hace de “GitHub Action”: exporta el snapshot con exportSnapshot(), lo sube al bucket y avisa a la app.
El snapshotLoader lee el JSON directamente del bucket.
¿Cómo funciona?
- Origen: el bucket. El
snapshotLoaderhacefetchdel JSON concache: 'no-store'. - Trigger: un
POSTal endpoint de refresco.
Ese POST se emite justo después de subir el snapshot al bucket.
¿Cuándo elegirla?
Elígela cuando quieras que generar el snapshot no dependa de tu app.
El JSON lo produce y sube al bucket cualquier proceso externo —tu CI, un cron, un worker—. Las instancias solo lo leen.
Encaja especialmente bien si ya tienes almacenamiento de objetos (S3, R2, Blob Storage) en tu infraestructura.
Paso a paso
Parte del proyecto de la versión por endpoint.
Solo cambian el snapshotLoader y el paso de subir al bucket.
-
Levanta MinIO (S3 en local):
Ventana de terminal docker run -d --name minio -p 9000:9000 -p 9001:9001 \-e MINIO_ROOT_USER=minioadmin -e MINIO_ROOT_PASSWORD=minioadmin \quay.io/minio/minio server /data --console-address ":9001" -
Crea el bucket y hazlo de lectura pública con el cliente
mc(vía Docker, sin instalar nada):Ventana de terminal alias mc='docker run --rm -i --network=host \-e MC_HOST_local=http://minioadmin:minioadmin@localhost:9000 quay.io/minio/mc'mc mb local/content-island # crea el bucketmc anonymous set download local/content-island # GET público -
Cambia el
snapshotLoaderpara leer del bucket y añade la URL al.env. El resto desrc/lib/content-island.ts(el singleton yensureSnapshot()) no cambia respecto a la versión por endpoint:src/lib/content-island.ts snapshotLoader: async () => {const res = await fetch(process.env.SNAPSHOT_URL!, { cache: 'no-store' });if (!res.ok) {// Falla ruidosamente: si no, el cuerpo del error (p. ej. un XML 403) se// entregaría al cliente como si fuera el snapshot.throw new Error(`Failed to fetch snapshot: ${res.status} ${res.statusText}`);}return res.text();},.env SNAPSHOT_URL=http://localhost:9000/content-island/content-island-snapshot.json -
Crea el script de ejemplo. Solo en local hace las veces de la GitHub Action: exporta, sube al bucket y avisa a la app (en producción, ese trabajo lo hace tu CI). Sourcea el
.envél mismo para tener a manoCONTENT_ISLAND_TOKENyREFRESH_SECRET:scripts/publish-bucket.sh #!/usr/bin/env bashset -euo pipefailexport AWS_ACCESS_KEY_ID=minioadminexport AWS_SECRET_ACCESS_KEY=minioadminexport AWS_DEFAULT_REGION=us-east-1# Toma CONTENT_ISLAND_TOKEN y REFRESH_SECRET del .envset -a; source "$(dirname "$0")/../.env"; set +a# 1) Exporta el snapshot desde Content Islandnpx content-island export \--access-token "$CONTENT_ISLAND_TOKEN" \--snapshot-path ./content-island-snapshot.json# 2) Súbelo al bucket (MinIO habla S3; solo cambia el --endpoint-url)aws --endpoint-url http://localhost:9000 s3 cp \./content-island-snapshot.json \s3://content-island/content-island-snapshot.json# 3) Avisa a la app para que recargue en memoriacurl -fsS -X POST http://localhost:3000/api/content-island/refresh \-H "x-refresh-secret: $REFRESH_SECRET"echo "✅ snapshot publicado y app avisada"Añade un script en el
package.jsonpara lanzarlo cómodamente:package.json "scripts": {"publish:bucket": "bash scripts/publish-bucket.sh"} -
Pruébalo. Haz una primera carga en el bucket antes de arrancar (si no, el primer refresco no encuentra JSON):
Ventana de terminal npm run publish:bucket # exporta, sube al bucket y avisa a la appnpm run dev # http://localhost:3000curl http://localhost:3000/ # { "exportedAt": "...", "count": N }Cada vez que publiques contenido, vuelve a lanzar
npm run publish:buckety repite elcurl:exportedAtcambiará sin reiniciar el servidor.
El mismo flujo en GitHub Actions
Así se llevaría el script local a un workflow de CD. Es orientativo: adáptalo a tu proveedor de bucket y a tu despliegue.
Mantiene el mismo orden que en local (primero sube, luego avisa) y se lanza con repository_dispatch desde el webhook de Content Island (o a mano con workflow_dispatch):
name: Publicar snapshot en el bucket
on: workflow_dispatch: repository_dispatch: types: [content-refresh]
jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/setup-node@v4 with: node-version: 20
# 1) Exporta el snapshot desde Content Island - name: Exportar snapshot env: CONTENT_ISLAND_TOKEN: ${{ secrets.CONTENT_ISLAND_TOKEN }} run: npx content-island export --access-token "$CONTENT_ISLAND_TOKEN" --snapshot-path ./content-island-snapshot.json
# 2) Súbelo al bucket (aquí S3 real; adáptalo a tu proveedor) - name: Configurar credenciales de AWS uses: aws-actions/configure-aws-credentials@v4 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: ${{ secrets.AWS_REGION }} - name: Subir al bucket run: aws s3 cp ./content-island-snapshot.json "s3://${{ secrets.SNAPSHOT_BUCKET }}/content-island-snapshot.json"
# 3) Avisa a la app para que recargue en memoria - name: Avisar a la app env: REFRESH_URL: ${{ secrets.REFRESH_URL }} REFRESH_SECRET: ${{ secrets.REFRESH_SECRET }} run: curl -fsS -X POST "$REFRESH_URL" -H "x-refresh-secret: $REFRESH_SECRET"Ejemplo
Código completo (incluye el docker-compose.yml): content-island/examples-dynamic-snapshot → express/02-bucket-s3.