Salta ai contenuti

Build, code e concorrenza

npm run build esegue, nell’ordine:

  1. sync del Content Layer: per ogni collezione chiama il loader;
  2. fetch REST: una richiesta per CPT, più una batch per le immagini di ogni collezione che ne ha;
  3. validazione Zod + mapping e scrittura nello store;
  4. rendering delle pagine (getStaticPaths per i dettagli);
  5. asset: CSS, JS delle isole, sitemap, file pubblici.

Ordine di grandezza: ~14 richieste HTTP per una build completa (10 collezioni + 4–5 batch immagini + impostazioni). I tempi dipendono da WordPress e dalla rete, non dal numero di pagine.

Ogni collezione ha il suo loader. Astro li esegue durante il sync e ognuno gestisce i propri errori:

  • un loader che fallisce non “avvelena” gli altri;
  • con ENABLE_HTML_FALLBACK=false il primo errore fa fallire la build (voluto);
  • con true il loader logga un warning e lascia la collezione vuota.

Non c’è stato condiviso tra i loader, nessuna coda applicativa, nessun lock: ogni loader fa le sue richieste. L’unica ottimizzazione è il batch dei media, che evita N richieste per N immagini.

Il sito è statico: i contenuti sono scritti dentro l’HTML. Non esiste un database da interrogare a runtime. Quindi:

  • modifica su WordPress → il sito pubblico non cambia;
  • serve rifare la build e ripubblicare i file.

È il prezzo dello statico. Per un sito vetrina va bene; per contenuti che cambiano ogni minuto no (vedi Perché questo template).

headless-webhook.php intercetta:

  • wp_after_insert_post per i CPT del sito e per le pagine;
  • updated_option per le opzioni headless_* (contatti, social, legali, toggle).

Al salvataggio:

  1. imposta un transient di 60 secondi (debounce);
  2. su shutdown chiama GitHub repository_dispatch con event_type: wp-content-update;
  3. il workflow deploy.yml ascolta quell’evento e parte.

Perché il debounce: un editor che salva cinque volte in un minuto deve produrre una build, non cinque. Perché blocking: false: l’admin non deve aspettare la rete. Perché il webhook non contiene logica di build: WordPress notifica, GitHub costruisce.

Ci sono tre livelli distinti. Non sono la stessa cosa.

Astro sincronizza le collezioni; i loader non condividono risorse. Le richieste HTTP possono essere concorrenti, ma non c’è una coda nostra da gestire. Il risultato è uno snapshot coerente dei contenuti al momento della build.

Il debounce da 60 secondi collassa le raffiche. Se due modifiche avvengono a più di 60 secondi di distanza, partono due dispatch.

concurrency:
group: deploy
cancel-in-progress: false
  • group: deploy: al massimo un deploy alla volta per repository.
  • cancel-in-progress: false: i run in coda non vengono cancellati.

Cosa succede con tre dispatch ravvicinati ma fuori dal debounce:

  1. il primo run parte;
  2. gli altri restano in coda;
  3. ogni run rifà la build leggendo lo stato attuale di WordPress.

Quindi l’ultimo run pubblica lo stato più recente. I run intermedi sono ridondanti ma innocui. Cancellare quelli in coda (cancel-in-progress: true) ridurrebbe i minuti, ma rischierebbe di interrompere un sync FTPS a metà. Per un sito vetrina la scelta conservativa è corretta.

  • Non è una coda di messaggi: nessun worker, nessun retry automatico.
  • Non è un lock sui contenuti: WordPress può cambiare durante la build.
  • Non è un sistema di preview: non c’è modo di vedere la modifica prima del deploy.

Il loader legge per_page=100. Oltre 100 elementi per CPT servono pagine:

const all: MyItem[] = [];
let page = 1;
while (true) {
const batch = schema.parse(
await fetchWordPressJson({
endpoint,
searchParams: { per_page: 100, page, _fields: "..." },
}),
) as MyItem[];
all.push(...batch);
if (batch.length < 100) break;
page++;
}

Aggiungilo quando serve davvero, non prima: il limite è documentato e la build fallisce in modo silenzioso solo se superi i 100 senza accorgertene. Se prevedi cataloghi grandi, mettilo subito e aggiungi un test.

  • cache delle risposte REST tra build (token, ETag);
  • richieste parallele esplicite per collezione;
  • build incrementali (Astro non le fa per i loader custom);
  • preview separata con ENABLE_HTML_FALLBACK=true e un sottoinsieme di contenuti.

Ognuna aggiunge complessità. Il template preferisce una build semplice e prevedibile.