Quando gli articoli del blog cominciano ad essere numerosi, generare tutti gli elementi della collezione al momento della generazione della pagina potrebbe non essere la soluzione ottimale in termini di prestazioni.
Per ottimizzare le prestazioni del blog, dobbiamo adottare un approccio dinamico, generando il contenuto solo al momento della richiesta dell’utente. Dobbiamo passare dal rendering statico (SSG) al rendering dinamico (SSR).
Rendering on-demand in un blog dinamico con Astro e htmx
Di default, un sito Astro è statico. Ciò vuol dire che il rendering avviene al momento della build. Quando il client dell’utente invia una richiesta al server esso fornirà pagine statiche. Questo permette di ottenere siti dalle prestazioni notevoli. Non vi sono infatti database da interrogare o task server side da eseguire.
Tuttavia, quando è necessario eseguire operazioni lato server, come ad esempio il recupero di dati da un database, oppure una nuova interrogazione di una collezione di contenuti, è necessario passare ad una modalità di rendering Server-Side. Ciò in modo che le pagine vengano generate dinamicamente al momento della richiesta dell’utente.
La modalità SSR (Server-Side Rendering) richiede un server Node.js in esecuzione in produzione per generare le pagine dinamicamente alla richiesta dell’utente (rendering on-demand). Questo a differenza della modalità SSG (Static Site Generator) che produce file statici distribuibili su qualsiasi hosting statico.
È necessario anche aggiungere un adattatore che abiliti il rendering on-demand nel nostro progetto Astro. Qui utilizzeremo l’adattatore di Node.js, ma Astro supporta anche adattatori, come Cloudflare, Vercel e Netlify. Per maggiori informazioni, si veda la documentazione online.
Interrompiamo l’esecuzione del server di sviluppo, se è in funzione, e digitiamo nel terminale dei comandi quanto segue:
npx astro add node
Il rendering on-demand delle pagine
Questo ci permette di abilitare il rendering on-demand per singola pagina o per l’intero sito. Al termine dell’operazione, apriamo il file astro.config.mjs
e verifichiamo che contenga il seguente codice:
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
import tailwindcss from '@tailwindcss/vite';
export default defineConfig({
adapter: node({
mode: 'standalone',
}),
vite: {
plugins: [tailwindcss()]
}
});
import node from '@astrojs/node'
importa l’adattatore di node per il Server-Side Rendering;adapter: node(...)
fa sì che il sito non sia statico ma dinamico, generato on-demand. La modalità standalone crea un pacchetto server completo di tutte le dipendenze e autonomo, in modo che possa essere eseguito su qualsiasi macchina su cui sia installato Node.js.
Verificato che il file contenga le istruzioni descritte, l’ultimo step per rendere dinamiche le pagine del progetto è aggiungere la seguente istruzione ad ogni singola pagina o endpoint che vogliamo rendere on-demand:
export const prerender = false;
Questa istruzione abilita il rendering dinamico (SSR) per una specifica pagina o endpoint. Le pagine senza questa istruzione rimarranno statiche. Questo permette di adottare un approccio ibrido con siti parzialmente dinamici. Più avanti vedremo in quali pagine del nostro progetto inserire questo codice.
Una pagina di archivio dinamica
Creiamo una nuova sezione del sito che rispecchi il blog creato nelle lezioni precedenti. Se il primo blog era statico, creato con l’integrazione di Alpine.js, questa nuova sezione, che chiameremo Archive
, è generata dinamicamente con l’integrazione di htmx.
Creiamo, quindi, la cartella /src/pages/archive
. All’interno di questa cartella aggiungiamo poi due file:
index.astro
load-more.astro
Il primo file conterrà il codice che genera l’HTML da fornire all’utente quando accede alla pagina di archivio.
Apriamo il file index.astro
e aggiungiamo il seguente frontmatter:
---
import { getCollection, type CollectionEntry } from 'astro:content';
import Layout from '../../layouts/Layout.astro';
import ArticleCard from '../../components/ArticleCard.astro';
const POSTS_PER_PAGE = 3;
const sortedPosts = (await getCollection('blog')).sort(
(a: CollectionEntry<'blog'>, b: CollectionEntry<'blog'>) =>
b.data.pubDate.getTime() - a.data.pubDate.getTime()
);
const initialPosts: CollectionEntry<'blog'>[] = sortedPosts.slice(0, POSTS_PER_PAGE);
const hasMore: boolean = sortedPosts.length > POSTS_PER_PAGE;
---
- Le prime tre istruzioni importano le dipendenze necessarie;
POSTS_PER_PAGE
stabilisce il numero di articoli da visualizzare per pagina;sortedPosts
è un array di oggettiCollectionEntry<'blog'>
ordinati per data di pubblicazione;initialPosts
è un array degli articoli iniziali da mostrare nella pagina di archivio, dall’indice 0 aPOSTS_PER_PAGE
;hasMore
è un booleano impostato atrue
sesortedPosts.length > POSTS_PER_PAGE
, ossia se il numero totale di post è maggiore del numero di post per pagina.
Nel successivo markup, utilizziamo i dati generati nel frontmatter per generare la struttura HTML della pagina. Come già detto, si tratta dei primi contenuti visibili al momento del caricamento.
<Layout title="Archivio Articoli" description="Caricamento progressivo con HTMX.">
<h1 class="text-4xl font-extrabold text-gray-900 mb-8">Archivio</h1>
<div id="posts-container" class="grid gap-10">
{initialPosts.map(post => (
<ArticleCard post={post} />
))}
<div id="load-more-row" class="mt-12 text-center">
<!-- Mostra il pulsante solo se ci sono altri post -->
{hasMore && (
<button
type="button"
hx-get="/archive/load-more"
hx-vals='{"page": 2}'
hx-target="#load-more-row"
hx-swap="outerHTML"
class="bg-blue-600 text-white py-3 px-6 rounded-lg hover:bg-blue-700"
>
Carica Altri Articoli
</button>
)}
</div>
</div>
</Layout>
Analisi del codice
Vediamo gli elementi principali di questo blocco di codice:
- Tutto il contenuto della pagina è racchiuso nel componente
Layout
. Quindi, l’header, il footer e i meta tag saranno gli stessi del blog. Gli attributititle
edescription
sono le proprietà che vengono passate verso l’alto al componente. - La
div#post-container
è il contenitore di tutti gli articoli. initialPosts.map
itera tra gli elementi dell’arrayinitialPosts
che in questo esempio contiene tre articoli.<ArticleCard post={post} />
renderizza il componenteArticleCard
, passando ad ogni istanza la proprietàpost
.- La successiva
div#load-more-row
è il contenitore del pulsante “Carica Altri Articoli”. - Se
hasMore
ètrue
, viene generato il pulsante “Carica Altri Articoli” con gli attributi htmx: hx-get
invia una richiesta GET all’URL specificato.hx-vals
aggiunge i parametri da trasmettere con l’URL.{"page": 2}
specifica la pagina iniziale da caricare. Avremmo potuto fare la stessa cosa aggiungendo una variabile alla querystring (/archive/load-more?page=2
).hx-target
specifica l’elemento del DOM che riceverà il codice della risposta. Nel nostro esempio è lo stesso contenitore del pulsante.hx-swap
stabilisce come aggiornare l’elemento target. In questo caso,outerHTML
fa sì che venga sostituito l’intero elemento target, incluso l’elemento stesso.
Ora possiamo aggiungere un nuovo link al menu di navigazione nel layout generale del sito. Apriamo il file /src/layouts/Layout.astro
e modifichiamo il menu come segue:
<nav class="max-w-4xl mx-auto px-6 py-4 flex justify-between items-center">
<a href="https://www.html.it/" class="text-xl font-bold text-purple-700">{title}</a>
<div class="flex items-center gap-x-6">
<a href="https://www.html.it/" class="text-gray-600 hover:text-purple-700 mr-4">Home</a>
<a href="http://www.html.it/blog" class="text-gray-600 hover:text-purple-700">Blog</a>
<a href="http://www.html.it/archive" class="text-gray-600 hover:text-purple-700">Archive</a>
</div>
</nav>

Archivio di articoli pronto per il rendering on-demand
Dopo questo approfondimento dedicato alla creazione di un blog dinamico con Astro e htmx, nella prossima lezione, creeremo l’endpoint /archive/load-more.astro
per gestire il caricamento dinamico dei post successivi, integrando htmx con Astro SSR per generare una paginazione dinamica e scalabile.
Se vuoi aggiornamenti su Un blog dinamico con Astro e htmx inserisci la tua email nel box qui sotto: