Lavoro · 2026
Questo sito: un portfolio trilingue i cui test girano sul build
Astro 5, tre lingue con URL localizzati, e una suite di test che legge dist/ invece del sorgente. Compreso il bug che quella suite non poteva vedere, e che ha mandato lorem ipsum in produzione.
Il sito che stai leggendo. È un sito Astro 5, prerenderizzato in HTML statico, pubblicato in inglese, italiano e francese. Lo racconto qui soprattutto per una decisione e un fallimento.
URL che appartengono alla loro lingua
L’inglese sta alla radice senza prefisso; l’italiano vive sotto /it/, il
francese sotto /fr/. La parte che costa lavoro vero è che anche i segmenti di
percorso sono tradotti, non solo i contenuti:
| Inglese | Italiano | Francese |
|---|---|---|
/projects/ | /it/progetti/ | /fr/projets/ |
/about/ | /it/chi-sono/ | /fr/a-propos/ |
/hobbies/ | /it/hobby/ | /fr/loisirs/ |
Un progetto è un file Markdown per lingua che condivide uno slug, ed è quello
a permettere alle tre versioni di puntarsi a vicenda. Una pagina deve esistere
solo nelle lingue per cui c’è contenuto: l’hreflang viene emesso verso le
traduzioni che esistono davvero, mai verso un URL che darebbe 404. Ogni pagina
referenzia anche se stessa, cosa che la specifica richiede e che è la regola che
più spesso viene saltata.
I test leggono il build, non il sorgente
La suite — quindici file, 64 test sul test runner di Node — lancia prima
npm run build e poi legge dist/. Non c’è niente di simulato, perché la cosa
che vale la pena verificare è l’HTML che un browser riceverà davvero.
Controlla che nessun link interno sia rotto, che ogni pagina abbia title e
description non vuoti e che quelle di primo livello siano distinte, che ogni
immagine referenziata localmente esista su disco, che i set di hreflang siano
completi e auto-referenzianti, che i draft non finiscano mai nel build, e che il
tema sia applicato prima del primo paint perché la pagina non lampeggi bianca.
Verificare sul build intercetta un’intera categoria di bug che i test sul sorgente si perdono: un link corretto dentro un componente e sbagliato dopo il routing, un percorso immagine che si risolveva in sviluppo e non nell’output.
Il bug che i test non potevano vedere
Ecco la parte che vale la pena raccontare. Per settimane questo sito ha servito cinque progetti chiamati cose tipo “Lorem ipsum ingestion pipeline”, e il footer di ogni singola pagina diceva “Lorem ipsum dolor sit amet”. La suite di test è rimasta verde per tutto il tempo.
Era verde perché ogni asserzione era strutturale. I fixture che i test usavano per verificare il comportamento — un progetto pubblicato, uno non in evidenza, uno in bozza — erano gli stessi file che venivano pubblicati come contenuto. La suite chiedeva c’è un progetto elencato in questa pagina, e un progetto c’era. Non aveva nessuna opinione su cosa quel progetto dicesse.
È la trappola dell’usare i propri fixture come contenuto. I test continuano a passare, con precisione e onestà, mentre il sito non dice niente di vero. Una suite verde ti dice che la struttura regge; non può dirti che la struttura stia reggendo qualcosa di reale.
Quello che l’ha risolto non è stata un’asserzione più furba, è stato cancellare i fixture e puntare i test su progetti che esistono. La copertura sul comportamento è invariata — qualcosa deve comunque essere pubblicato-ma-non-in-evidenza perché quel test voglia dire qualcosa, e quel qualcosa adesso è questa pagina.
Quello che /projects/ ha servito per settimane, ricostruito dal commit precedente alla pulizia. Tutti i test erano verdi su questa pagina.
Pubblicare apposta
Il build viene sincronizzato via rsync verso il suo host, e npm run deploy
gira in --dry-run di default. Per pubblicare davvero serve un -- --live
esplicito, e a quel punto lo script chiede all’operatore di digitare il nome del
dominio prima di procedere.
Quell’attrito è voluto, perché rsync gira con --delete: tutto quello che sta
sul server e non sta in dist/ sparisce. Un flag così distruttivo non dovrebbe
stare a un refuso di distanza da una sera stanca.
Copertine senza una chiamata di rete
Ogni progetto, articolo e pagina disegna una copertina. Se il campo cover: non
è impostato, si ricade su un SVG piatto generato in modo deterministico dallo
slug da uno script — stesso slug, stessa immagine, ogni volta, senza scaricare
font o immagini da nessuna parte. Un test fa fallire il build se una pagina
referenzia una copertina che non esiste, ed è così che una copertina mancante
diventa un test rosso invece che un’immagine rotta.