Come ho fatto il portfolio che stai guardando

Intro

Il mio portfolio è nato come un PDF statico, difficile da modificare e non proprio moderno. Sono passato a Claude Design e le cose sono cambiate poco. I testi stavano dentro la pagina e per aggiungere un progetto dovevo riaprire lo strumento. Le piattaforme no-code sono comode, ma limitano la flessibilità e il controllo che cercavo. Serviva un sito vero, tutto mio.

La home del sito: hero, titolo e prima card di lavoro
Il contesto

Cosa volevo da un sito vero

Volevo un sito che mi rappresentasse a ogni scala, dalla struttura all'ultimo dettaglio, e per questo sono partito da un design system: colori, tipografia e spaziature decisi una volta sola e usati ovunque. Il tema scuro è venuto da lì, dando un secondo valore a ogni colore. E volevo provare una cosa nuova, sviluppare in vibecoding, cioè decidere io cosa deve fare il sito e lasciare che sia un'AI a scrivere il codice.

L'apertura della pagina About, il ritratto illustrato e una citazione di Brian O'Driscoll, divisa a metà fra tema chiaro e tema scuro
La sezione Ispirazione di About: filtri per libri, podcast e serie, e una fila di copertine
Flusso completo

Io decidevo, l'AI scriveva

Il primo scaffold che l'AI mi ha consegnato aveva quattro strumenti, ma le interazioni della home erano tre in tutto e per gestirle ne bastava uno, quindi ho tolto gli altri.

"dependencies": {
-  "@astrojs/react": "^6.0.2",
-  "@tailwindcss/vite": "^4.3.3",
-  "@types/react": "^19.2.18",
-  "@types/react-dom": "^19.2.4",
   "astro": "^7.2.0",
-  "gsap": "^3.15.0",
+  "marked": "^18.0.9",
-  "react": "^19.2.8",
-  "react-dom": "^19.2.8",
-  "tailwindcss": "^4.3.3"
}
package.json, la mattina e la sera del 10 agosto 2026

Non tutte le decisioni erano tecniche. Sul telefono i tre pilastri della home erano impilati e centrati, e da soli occupavano metà della sezione, così il primo progetto arrivava dopo più di due schermate di scroll. Li ho messi in riga, con l'icona a sinistra e il testo accanto, e il lavoro ora arriva dopo una schermata e mezza.

I tre pilastri della home a 390px, impilati e centrati: 694 pixel di altezza
Gli stessi pilastri in riga, con l'icona a sinistra: 327 pixel di altezza

Un'immagine che sul mio computer era nitida usciva sgranata sul sito. L'AI mi ha proposto tre soluzioni, consigliandone una, e le ho scartate tutte chiedendo invece se bastasse cambiare la risoluzione. Non bastava, ma la domanda ha spostato il problema: partire da un'immagine grande e lasciare che il browser la riducesse la ammorbidiva. La soluzione è stata esportare da Figma due versioni alla larghezza esatta con cui compare nella pagina, in 1× e in 2×.

Claude Code

Non è l'immagine a essere sfocata: è mostrata piccola. (…) Si risolve solo dandole più pixel di schermo: 1. clic per ingrandire, 2. figure più grandi in pagina, 3. ritagliare le schermate. (…) Io farei la 1.

Io

La 1 non mi piace, la due non si può fare e la tre no. Se cambiamo la risoluzione risolviamo?

Claude Code

Cambiare solo la risoluzione del file no: qualsiasi immagine più grande, ridotta a 1300 pixel, dà lo stesso risultato. C'è però un modo di esportare che può aiutare davvero, e non tocca il layout.

Dalla chat con Claude Code, 12 ottobre 2026

Quando controllare tutto a occhio è diventato lungo e poco affidabile, ho fatto scrivere all'AI una suite di test su due livelli. Il primo gira in tre secondi sulla build finita e verifica le cose che si rompono in silenzio, come link, asset, tag SEO e script inline. Il secondo apre il sito in un browser vero con Playwright, prova le interazioni, controlla l'overflow a 390, 800 e 1440 pixel, passa axe per l'accessibilità e confronta gli screenshot con una baseline. L'ottica era controllare i guasti meccanici e lasciare a me il giudizio sul look and feel, per questo il confronto visivo segnala le differenze e non blocca niente.

$ pnpm test
▸ build…
  ok in 2.0 s
▸ controlli statici, browser e confronto visivo…
✔ /: ogni link e asset interno esiste in dist/
✔ CSP: ogni <script> inline di ogni pagina ha il suo hash
✔ carousel Works: il pallino N porta alla slide N e aggiorna aria-current
✔ /about/: nessun overflow orizzontale a 390px (mobile)
✔ / a 1440px: nessuna violazione axe seria o critica
…
ℹ tests 118
ℹ pass 118
ℹ fail 0

◎ confronto visivo: 12 da guardare su 21
Terminale: pnpm test, 18 ottobre 2026

Con la stessa logica ho fatto aggiungere la sicurezza. A ogni build uno script scrive gli header e una Content Security Policy che elenca l'hash di ogni script inline, così uno script nuovo è coperto da solo e uno esterno viene bloccato. Il sito non carica niente da altri domini, e se un controllo è rosso, che si tratti di una dipendenza con una vulnerabilità alta o di uno script senza hash, un hook ferma il push su main, che su Cloudflare vuol dire pubblicare.

▸ push su main: lancio la suite di test prima di pubblicare
▸ build…
  ok in 2.5 s
▸ controlli statici, browser e confronto visivo…
✖ /: nessun errore JS e nessuna risorsa mancante
✖ nessuna pagina carica script, stili o font da un altro dominio
…
ℹ tests 118
ℹ pass 110
ℹ fail 8

  Loading the script 'https://cdn.example.com/analytics.js' violates
  the following Content Security Policy directive (…)
  The action has been blocked.

✗ suite rossa: il push su main è stato fermato.
  Per pubblicare comunque: git push --no-verify
Terminale: l'hook prima di un push su main, con uno script esterno aggiunto apposta
Risultati

Online, e leggero

I numeri sono misurati sul sito online, non sulla versione che gira sul mio computer.

100
Su Lighthouse, in tutte e quattro le categorie

Prestazioni, accessibilità, best practice e SEO, su mobile. PageSpeed Insights, 18 ottobre 2026.

3 KB di JavaScript al massimo

Per pagina. Il resto è HTML e CSS.

145 KB la home

Al primo caricamento.

“Il codice non è mai stato la parte difficile. La parte difficile è stata decidere cosa non serviva.”

Learnings

Oggi aprirei con la lista di cosa serve

Un'AI che scrive codice tende ad aggiungere. Durante il lavoro mi ha proposto librerie per un effetto solo, pagine e campi nuovi per differenze che a occhio non si vedevano, componenti dove bastava un'immagine e controlli pensati per un team, su un sito a cui lavoro da solo. Li ho rifiutati uno alla volta. Oggi aprirei il progetto con la lista di cosa il sito deve fare, e la userei per rifiutarli tutti insieme.