Portfolio — questo sito
Il sito che stai guardando: portfolio full-stack con CMS headless proprio e una scena 3D nell'hero costruita a mano, non un template.
- Next.js 16
- React 19
- TypeScript
- Tailwind CSS 4
- Sanity CMS
- React Three Fiber
- Netlify

Il sito che stai guardando in questo momento è a sua volta un case study: un portfolio costruito da zero, senza template, con un CMS headless proprio per gestire i contenuti senza toccare il codice, e una scena 3D nell'hero costruita a mano invece di un asset scaricato.
Il problema
Un redesign completo del portfolio precedente, con un vincolo preciso: doveva servire sia per candidature come sviluppatore sia come vetrina per procacciare clienti freelance, senza sembrare né un tema WordPress riadattato né un sito "wow" ma vuoto di contenuto tecnico reale. Ogni case study doveva poter raccontare architetture diverse (RAG con pgvector, app Flutter, motori di regole, estensioni ORM) senza forzarle in un template a sezioni fisse identiche.
Architettura
Next.js 16 (App Router) come framework full-stack unico, con le pagine principali raggruppate in un route group (site) che condivide header/footer, mentre /studio (Sanity Studio embedded) e /link restano fuori dal gruppo per non ereditare la UI del sito — un dettaglio che sembra ovvio in retrospettiva ma che nella prima versione non c'era: lo Studio si caricava incastrato tra header e footer del sito pubblico.
Sanity CMS gestisce tutti i contenuti: progetti, profilo, home, competenze, pagina /link. Il corpo di ogni case study è un campo Portable Text libero — non sezioni fisse — con tipi custom per tabelle, blocchi di codice (evidenziati server-side con Shiki, tema dual light/dark), diagrammi di architettura (nodi/archi raggruppati per layer, resi come componente React con Framer Motion, non testo monospace) e griglie di numeri.
Architettura
Client
Browser
Backend
Next.js 16 (App Router)
Full-stack monolite
Dati
Sanity CMS
Contenuti headless
Servizio esterno
Resend
Form di contatto
Infrastruttura
Netlify
Build + deploy
100
Lighthouse su tutte le pagine
5
case study pubblicati
6
tipi di documento CMS
3
bug reali risolti in produzione
La scena 3D dell'hero
Un simbolo "</>" costruito da cinque barre di vetro (RoundedBox con meshPhysicalMaterial a transmission, illuminate da un Environment procedurale di Lightformer invece di una texture HDR da scaricare) al posto di una forma astratta — la prima versione era un torus knot con materiale a distorsione organica, sostituito dopo un feedback diretto: "voglio qualcosa più sul tema dev, non a caso". Le barre si materializzano in sequenza all'ingresso (< poi / poi >, con un piccolo rimbalzo easeOutBack), e il canvas è caricato con next/dynamic e ssr:false per non bloccare l'LCP testuale.
Un hook (useSceneCapability, basato su useSyncExternalStore, non su useEffect+setState) decide se montare la scena o un fallback statico, verificando prefers-reduced-motion, hardware a bassa potenza (core/RAM) e supporto WebGL — inizialmente escludeva anche tutti i dispositivi touch per prudenza, poi rimosso su richiesta esplicita perché i telefoni moderni reggono bene una scena così semplice.
Bug reali risolti durante lo sviluppo
- Dataset Sanity privato di default: un secondo dataset creato per errore (oltre a quello pubblico standard) aveva
aclMode: "private"— le query pubbliche restituivano silenziosamente un array vuoto invece di un errore, mascherando il problema per diverse verifiche. Risolto impostando la visibilità a pubblica via API di gestione Sanity. - Dipendenza a monte rotta:
sanity@6.12.0(l'ultima versione al momento) trascina@sanity/sdk-react@3.0.0, che dipende in modo esatto da@sanity/workbench@0.1.0-alpha.24— un pacchetto alpha che espone solo sorgenti TypeScript grezzi invece di un bundle compilato. Sia Turbopack che webpack fallivano a parsarlo, rompendo/studiocon un errore 500. Risolto pinnandosanitye@sanity/visiona6.11.0, l'ultima versione prima dell'introduzione di quella dipendenza. - Blocco cross-origin in sviluppo: aprendo il sito da un altro dispositivo sulla rete locale (per testare su telefono reale), Next.js bloccava silenziosamente le risorse HMR da un'origine non in whitelist — l'HTML iniziale rispondeva comunque 200, ma l'idratazione client falliva e ogni sezione animata restava invisibile. Risolto con
allowedDevOriginsinnext.config.ts. - Contrasto colore e ordine heading: un audit Lighthouse ha rilevato testo a opacità troppo bassa (rapporto di contrasto 3.5 invece di 4.5:1 richiesto) nel componente dei diagrammi di architettura, e un heading decorativo che rompeva l'ordine sequenziale h1→h2→h3. Corretti entrambi; tutte le pagine sono tornate a 100/100/100/100 su performance, accessibilità, best practice e SEO.
Contenuti e SEO
Ogni pagina ha metadata dedicati con un title template globale, canonical, Open Graph e Twitter Card. JSON-LD strutturato (Person su home e chi-sono, CreativeWork e BreadcrumbList per ogni progetto). Sitemap e robots.txt generati nativamente da Next.js (niente next-sitemap, la file convention sitemap.ts/robots.ts di Next 16 copre lo stesso caso senza dipendenze extra). Immagini Open Graph generate dinamicamente per ogni case study con next/og, incluso il logo del sito incorporato come data URI.
Cosa ho imparato
La parte più utile non è stata scrivere il codice del sito, ma verificarlo davvero: eseguire build di produzione reali, avviare il server e controllarlo con Playwright invece di fidarmi del solo type-check, e lasciare che un audit Lighthouse trovasse problemi concreti invece di assumerli. Diversi dei bug elencati sopra — il dataset privato, la dipendenza rotta, il blocco cross-origin — non sarebbero emersi da una semplice lettura del codice: sono emersi solo provando il sito come lo proverebbe un utente vero.