In produzione

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
Portfolio — questo sito

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

BrowserNext.js 16 (App Router)Next.js 16 (App Router)Sanity CMSNext.js 16 (App Router)ResendNext.js 16 (App Router)Netlify· deploySanity CMSNetlify· webhook rebuild

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 /studio con un errore 500. Risolto pinnando sanity e @sanity/vision a 6.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 allowedDevOrigins in next.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.