Questo pezzo è per chi ha un sito Astro che prende i contenuti da un CMS headless e si chiede se le Live Content Collections possano togliere di mezzo il rebuild a ogni modifica. La risposta breve è sì, ma il costo si sposta dalla build alla richiesta, e la cache diventa un lavoro tuo. Tutto quello che segue viene dalla documentazione ufficiale di Astro e dagli annunci di rilascio, letti il 29 ottobre 2026 con Astro 7.4.4 e @astrojs/cloudflare 14.4.3 come ultime versioni su npm. Non ho fatto test né misure di build: nessun numero qui è mio.
In breve:
- Le Live Content Collections caricano i dati a ogni richiesta invece che in build. Sono sperimentali da Astro 5.10 (giugno 2025) e stabili da Astro 6 (10 marzo 2026).
- Si definiscono in
src/live.config.tscondefineLiveCollection(), si leggono congetLiveCollection()egetLiveEntry(), e serve un adapter per il rendering on demand. - Non esistono live loader integrati: lo scrivi tu, o usi quello del tuo CMS. Il loader implementa
loadCollection()eloadEntry()e restituisce i dati o un errore. - Limiti dichiarati: niente MDX, niente ottimizzazione delle immagini, niente data store persistente, una chiamata al CMS per richiesta se non metti una cache.
- La cache arriva con Astro 7 (22 giugno 2026): route caching stabile, cache hint dei loader passati a
Astro.cache.set(), invalidazione per tag o per path, e un provider sperimentale per la CDN di Cloudflare.
Il problema che risolvono
Con le content collection classiche, quelle di build, i dati del CMS vengono scaricati quando lanci astro build e salvati in un data store. Le pagine escono veloci, ma ogni correzione di una virgola nel CMS richiede una nuova build e un nuovo deploy, di solito attivati da un webhook.
Le Live Content Collections ribaltano il momento: i dati si chiedono al CMS quando arriva la richiesta, con le stesse idee di base (loader, schema, funzioni di query). L'annuncio di Astro 6 lo dice senza giri di parole: il contenuto si aggiorna nel momento in cui viene pubblicato, senza passare dalla pipeline di build.
La stessa documentazione però consiglia di restare sulle collection di build "quando possibile" e di passare a quelle live solo quando il contenuto deve essere aggiornato in tempo reale e il costo in prestazioni è accettabile. I casi che cita sono dati che cambiano spesso (prezzi, disponibilità), filtri che dipendono dalla richiesta, e l'anteprima delle bozze per chi scrive nel CMS.
Build-time o live: cosa cambia in pratica
Aspetto | Collection di build | Live Content Collection |
|---|---|---|
Quando legge il CMS | Durante | A ogni richiesta, se non c'è cache |
File di configurazione |
|
|
Definizione |
|
|
Metodi del loader |
|
|
Query |
|
|
Loader pronti |
| Nessuno integrato |
MDX e ottimizzazione immagini | Sì | No |
Rendering della pagina | Può essere prerenderizzata | Serve un adapter per il rendering on demand |
Aggiornare un contenuto | Nuova build | Nessuna build, serve gestire la cache |
Le due cose convivono nello stesso progetto. Puoi tenere le pagine di documentazione in una collection di build e mettere in una live solo quello che cambia spesso.
Come si scrive un live loader
Un live loader, secondo il riferimento della Content Loader API, è un oggetto con un name e due metodi. loadCollection() restituisce un elenco di voci, loadEntry() una voce sola, undefined se non esiste, oppure un oggetto con error. Ogni voce ha un id, i data e, se il CMS ti dà già l'HTML, un campo rendered che abilita render() e il componente <Content />. Se rendered manca, <Content /> non mostra niente.
Uno schema minimo per un CMS che espone un'API JSON. fetchPosts e fetchPost sono funzioni tue, non di Astro:
// src/loaders/cms.ts
import type { LiveLoader } from 'astro/loaders';
import { fetchPosts, fetchPost, type Post } from './cms-client';
export function cmsLoader(config: { token: string }): LiveLoader<Post, { id: string }, { tag?: string }> {
return {
name: 'cms-loader',
loadCollection: async ({ filter }) => {
try {
const posts = await fetchPosts(config.token, filter);
return {
entries: posts.map((p) => ({ id: p.slug, data: p, cacheHint: { tags: [`post-${p.slug}`] } })),
cacheHint: { tags: ['posts'] },
};
} catch (error) {
return { error: new Error('CMS non raggiungibile', { cause: error }) };
}
},
loadEntry: async ({ filter }) => {
try {
const p = await fetchPost(config.token, filter.id);
if (!p) return undefined;
return {
id: p.slug,
data: p,
rendered: { html: p.html },
cacheHint: { tags: [`post-${p.slug}`], lastModified: new Date(p.updatedAt) },
};
} catch (error) {
return { error: new Error('Errore nel caricare il post', { cause: error }) };
}
},
};
}In src/live.config.ts registri la collection e, se vuoi, aggiungi uno schema Zod. Lo schema, quando c'è, ha la precedenza sui tipi del loader e viene validato a runtime.
Gli errori diventano un valore, non un'eccezione
Le funzioni live non lanciano eccezioni: restituiscono { entry, error } o { entries, error }. Secondo la guida l'errore può essere quello definito dal loader, un LiveEntryNotFoundError se la voce non esiste, un LiveCollectionValidationError se i dati non rispettano lo schema, un LiveCollectionCacheHintError se il cache hint non è valido, o un LiveCollectionError per tutto il resto, comprese le eccezioni lanciate dentro il loader.
Con un sito che dipende dal CMS a ogni richiesta, questo è il punto da progettare per primo. Se il CMS è giù, una collection di build continua a servire l'ultima versione buona. Una collection live ti restituisce un errore, e sta a te decidere se mostrare una pagina di errore, un 404 o una copia dalla cache.
---
// src/pages/blog/[slug].astro
import { getLiveEntry, render } from 'astro:content';
import { LiveEntryNotFoundError } from 'astro/content/runtime';
const { entry, error } = await getLiveEntry('blog', Astro.params.slug!);
if (error) {
if (error instanceof LiveEntryNotFoundError) return Astro.rewrite('/404');
return Astro.rewrite('/500');
}
const { Content } = await render(entry);
---
<h1>{entry.data.title}</h1>
<Content />La cache: in Astro 6 era un'idea, in Astro 7 è un'API
In Astro 6 il route caching era sperimentale e, come dice l'annuncio stesso, partiva con il solo provider in memoria, adatto soprattutto all'adapter Node. I loader potevano già restituire cache hint (tag e data di ultima modifica), ma toccava a te trasformarli in header.
Con Astro 7 il route caching è stabile. Configuri un provider una volta e poi, nella pagina, chiami Astro.cache.set() con maxAge, swr e tags. Il pezzo che conta per un CMS headless è l'integrazione con le collection live: passi a Astro.cache.set() la voce restituita da getLiveEntry() e Astro legge da sola i tag e il lastModified del loader. Con getLiveCollection() i cache hint della collection e delle singole voci vengono uniti: i tag si sommano e vince il lastModified più recente.
---
const { entry, error } = await getLiveEntry('blog', Astro.params.slug!);
if (error) return Astro.rewrite('/404');
Astro.cache.set(entry); // tag e lastModified dal loader
Astro.cache.set({ maxAge: 300, swr: 60 }); // 5 minuti fresca, 1 minuto stale mentre si rigenera
---Tre dettagli che la documentazione scrive e che è facile non vedere:
- I cache hint da soli non mettono in cache niente. Sono valori da passare al route caching o da usare per scrivere header a mano.
- In sviluppo la cache non c'è:
Astro.cache.enabledèfalseeset()einvalidate()non fanno nulla. Per vederla all'opera devi fare build e preview. - Più chiamate a
cache.set()nella stessa richiesta si fondono: permaxAge,swredetagvince l'ultima, i tag si accumulano. Middleware, layout, loader e pagina possono contribuire ciascuno.
Invalidare quando il CMS pubblica
Senza rebuild, l'evento che ti dice che un contenuto è cambiato resta il webhook del CMS. Cambia cosa ci fai: invece di lanciare una build, chiami un endpoint del sito che svuota la cache giusta con cache.invalidate(), per tag o per path. L'invalidazione per path è a corrispondenza esatta, senza wildcard.
// src/pages/api/cms-webhook.ts
import type { APIRoute } from 'astro';
export const POST: APIRoute = async ({ request, cache }) => {
if (request.headers.get('x-webhook-secret') !== import.meta.env.CMS_WEBHOOK_SECRET) {
return new Response('Non autorizzato', { status: 401 });
}
const { slug } = await request.json();
await cache.invalidate({ tags: [`post-${slug}`] });
await cache.invalidate({ tags: ['posts'] }); // le liste che contengono quel post
return Response.json({ ok: true });
};Il controllo del segreto non è un dettaglio: un endpoint che svuota la cache senza autenticazione è un modo comodo per farti cadere il sito addosso. Anche l'esempio ufficiale nell'annuncio di Astro 7 lo ricorda in un commento.
Su Cloudflare Workers
Un caso concreto è il deploy su Workers, dove gira anche fnbit con Astro ed EmDash (qui solo come contesto: non ho migrato niente e non ho numeri). Se arrivi da Pages, il percorso è in Migrare un sito Astro da Pages a Workers.
Dalla versione 14.0.0 l'adapter @astrojs/cloudflare esporta un provider di cache, cacheCloudflare(), da @astrojs/cloudflare/cache. È ancora sperimentale e va attivato a mano:
// astro.config.mjs
import { defineConfig } from 'astro/config';
import cloudflare from '@astrojs/cloudflare';
import { cacheCloudflare } from '@astrojs/cloudflare/cache';
export default defineConfig({
adapter: cloudflare(),
cache: { provider: cacheCloudflare() },
});Secondo la guida al caching, il provider imposta gli header Cloudflare-CDN-Cache-Control e Cache-Tag, supporta l'invalidazione per tag e per path, e l'adapter attiva la Workers Cache con le impostazioni predefinite. La configurazione si può cambiare, per esempio se vuoi che la cache sopravviva al deploy di una nuova versione. I provider CDN rispondono dalla rete del provider senza invocare la funzione, e ogni risposta viene taggata anche con il suo path, così l'invalidazione per path funziona anche dove il purge è solo per tag.
Prima di scegliere, tieni presente che "sperimentale" qui ha un significato preciso: la documentazione dice che in una release futura questi provider saranno attivati in automatico dagli adapter, quindi l'API può ancora cambiare.
Quando non conviene
- Il blog cambia poche volte al giorno e la build è veloce: una collection di build con un webhook che lancia il deploy resta più semplice, e le pagine possono restare statiche.
- Ti servono MDX o l'ottimizzazione delle immagini di Astro: nelle collection live non ci sono.
- Il CMS ha limiti di richieste bassi o è lento: senza cache ogni visita diventa una chiamata all'API.
- Non vuoi gestire l'invalidazione: con le collection live la freschezza del contenuto dipende da
maxAge,swre dai purge che fai tu.
Il caso in cui brillano è l'opposto: contenuti che cambiano di continuo, filtri per richiesta, e l'anteprima delle bozze per chi scrive, dove una build a ogni salvataggio non ha senso.
Per la scelta del CMS c'è il confronto Strapi vs EmDash vs WordPress. Se invece stai ancora decidendo il framework, Astro vs Next.js per un sito di contenuti parte da quanto JavaScript ti serve.
Cosa conviene fare adesso
- Fai l'elenco dei contenuti del CMS e segna quanto spesso cambiano. Solo quelli che cambiano più volte al giorno, o che servono in anteprima, sono candidati a una collection live.
- Prima di scrivere il loader, controlla se il tuo CMS ne offre già uno nella directory delle integrazioni di Astro.
- Fai restituire al loader i cache hint fin dal primo giorno: un tag per voce e uno per la lista, più
lastModified. - Decidi cosa mostra la pagina quando il CMS è giù, e scrivilo prima della pagina felice.
- Proteggi l'endpoint del webhook con un segreto e invalida per tag, non tutto il sito.
- Su Workers, prova
cacheCloudflare()in un ambiente di staging e rileggi la guida al caching prima di ogni aggiornamento di Astro: è ancora sperimentale.
Per lo stack completo su Cloudflare c'è Workers, D1 e R2, e per tornare indietro se un deploy si rompe versioni e rollback su Workers. Le lezioni sul CMS che usa fnbit sono in Cosa ho imparato usando EmDash in produzione, e le definizioni dei termini tecnici sono nel glossario.
Domande frequenti
01Le Live Content Collections sono stabili?
Sì. Sono state introdotte come sperimentali in Astro 5.10 a giugno 2025 e sono stabili da Astro 6, uscito il 10 marzo 2026. Chi le usava con il flag experimental.liveContentCollections deve solo toglierlo dalla configurazione, come indica la guida di aggiornamento ad Astro 6.
02Posso usare le Live Content Collections su un sito completamente statico?
No. I dati vengono letti a ogni richiesta, quindi serve un adapter configurato per il rendering on demand, per esempio quello di Cloudflare, Node, Netlify o Vercel. Le altre pagine del sito possono restare prerenderizzate e le collection di build possono convivere con quelle live.
03Le Live Content Collections mettono in cache i dati da sole?
No. Senza una cache, ogni richiesta chiama il CMS. I loader possono restituire cache hint con tag e data di ultima modifica, ma diventano cache solo se li passi ad Astro.cache.set() con un provider configurato, disponibile in forma stabile da Astro 7, o se li usi per scrivere tu gli header.
04Funzionano con MDX e con l'ottimizzazione delle immagini?
No. La documentazione elenca tra i limiti delle collection live che l'MDX non può essere renderizzato a runtime e che le immagini non possono essere elaborate a runtime. Se il CMS restituisce HTML, il loader può passarlo nel campo rendered e lo mostri con <Content />.
05Cosa succede se il CMS non risponde?
getLiveEntry() e getLiveCollection() non lanciano un'eccezione: restituiscono un oggetto error, che può essere l'errore del loader o un LiveCollectionError se il loader ha lanciato un'eccezione. Sta alla pagina decidere se mostrare una pagina di errore, un 404 o una risposta ancora valida dalla cache.
06Esiste un provider di cache per Cloudflare Workers?
Sì, cacheCloudflare() dall'adapter @astrojs/cloudflare dalla versione 14.0.0. È sperimentale e va attivato a mano nella configurazione. Imposta gli header Cloudflare-CDN-Cache-Control e Cache-Tag, supporta l'invalidazione per tag e per path e attiva la Workers Cache.



