Saltearse al contenido

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?

Diagrama: Dynamic Snapshot en TanStack Start con bucket S3. Un script exporta el snapshot, lo sube al bucket y avisa a la app; el snapshotLoader lee el JSON del bucket
  • Origen: el bucket. El snapshotLoader hace fetch del JSON con cache: 'no-store'.
  • Trigger: un POST al 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.

  1. 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"
  2. 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 bucket
    mc anonymous set download local/content-island # GET público
  3. Cambia el snapshotLoader para leer del bucket y añade la URL al .env. El resto de src/lib/content-island.ts (el singleton y ensureSnapshot()) 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
  4. 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 mano CONTENT_ISLAND_TOKEN y REFRESH_SECRET:

    scripts/publish-bucket.sh
    #!/usr/bin/env bash
    set -euo pipefail
    export AWS_ACCESS_KEY_ID=minioadmin
    export AWS_SECRET_ACCESS_KEY=minioadmin
    export AWS_DEFAULT_REGION=us-east-1
    # Toma CONTENT_ISLAND_TOKEN y REFRESH_SECRET del .env
    set -a; source "$(dirname "$0")/../.env"; set +a
    # 1) Exporta el snapshot desde Content Island
    npx 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 memoria
    curl -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.json para lanzarlo cómodamente:

    package.json
    "scripts": {
    "publish:bucket": "bash scripts/publish-bucket.sh"
    }
  5. 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 app
    npm run dev # http://localhost:3000
    curl http://localhost:3000/ # { "exportedAt": "...", "count": N }

    Cada vez que publiques contenido, vuelve a lanzar npm run publish:bucket y repite el curl: exportedAt cambiará 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):

.github/workflows/publish-bucket.yml
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-snapshotexpress/02-bucket-s3.

Referencias