Il contesto

Un sito che doveva fare due cose bene

Questo sito — quello che stai leggendo ora — è stato costruito con un obiettivo preciso: personal branding e lead generation, con una base tecnica abbastanza solida da reggere nel tempo senza bisogno di manutenzione continua.

Il vincolo principale era la semplicità operativa: deploy in un comando, contenuti gestibili senza toccare il codice, SEO tecnica corretta fin dall'inizio. Nessun CMS esterno, nessuna dipendenza da servizi terzi, nessun lock-in.

Identità digitale e SEO locale da zero
Stack e architettura

Next.js 16 App Router + MDX

La scelta di Next.js 16 con App Router non è stata scontata — avrei potuto usare Astro o un sito statico puro. Ho scelto Next.js per tre motivi: rendering ibrido (SSG per i contenuti, SSR dove serve), ecosistema maturo, e familiarità con il tooling.

App Router
Layout annidati, `generateStaticParams` per blog e portfolio, `generateMetadata` per SEO dinamica per ogni pagina e ogni articolo
MDX
Contenuti scritti in Markdown con componenti React inline. Nessun CMS: un file `.mdx` per ogni articolo e ogni case study, versionato in Git
CSS modulare
Architettura CSS divisa in quattro file: `common.css`, `site.css`, `blog.css`, `portfolio.css`. CSS vanilla con custom properties come sistema principale, Tailwind usato solo per utility sparse
TypeScript
Tipi per i frontmatter MDX, per i metadata delle pagine, per i componenti. Nessun `any`, tutto tipizzato dall'inizio
Struttura file
32 file, struttura `src/app/` con route groups, `content/blog/` e `content/portfolio/` per i contenuti MDX, `public/` per assets statici
Fonts & assets
Font caricati via `next/font` con preload automatico. Immagini in `.webp` e `.avif` per performance. Logo in PNG con preload esplicito nel `<head>` per evitare layout shift durante il caricamento della navbar.
SEO tecnica

JSON-LD, canonical URL e Open Graph

La SEO tecnica è stata progettata dall'inizio, non aggiunta dopo. Ogni tipo di pagina ha il suo schema JSON-LD appropriato:

01

Homepage — WebSite

Schema `WebSite` con `SearchAction` e `ItemList` per blog e portfolio. Canonical senza trailing slash, Open Graph con immagine dedicata `og-image.webp` (1200×630).

02

Articoli blog — Article

Schema `Article` con `headline`, `description`, `author`, `datePublished`, `image` per l'immagine dell'articolo, e `publisher` con `logo` che punta al logo del sito. Keywords estratte dal frontmatter MDX.

03

Case study — CreativeWork

Schema `CreativeWork` con `provider`, `author`, `keywords` dallo stack array del frontmatter. Immagine del progetto nel campo `image`.

04

Listing pages — ItemList

Le pagine `/blog` e `/portfolio` espongono un `ItemList` con tutti gli articoli/progetti indicizzati, per massimizzare la presenza nei rich results.

Il generateMetadata di Next.js costruisce dinamicamente per ogni pagina: title, description, canonical, og:url, og:image, twitter:card. Nessun meta tag scritto a mano.

Un dettaglio importante: tutti gli URL usano https://ekulos.com senza www — la coerenza tra canonical, og:url e JSON-LD è necessaria per evitare segnali contrastanti a Google.

Deploy e infrastruttura

Docker + Watchtower + GitHub Actions

Il sito gira su un home server, senza CDN né proxy cloud. Il deploy è completamente automatizzato:

GitHub Actions
CI/CD su push a `main`: build dell'immagine Docker, push su `ghcr.io`, tag con SHA del commit per rollback preciso
Docker Compose
Container Next.js standalone con `output: 'standalone'` in `next.config.ts` per immagini minimali. Volume per assets statici
Watchtower
Polling automatico su `ghcr.io`: quando arriva una nuova immagine, Watchtower aggiorna il container in running senza downtime manuale
Justfile
Comandi operativi raccolti in un `Justfile`: `just deploy`, `just logs`, `just release`, `just clean`. Zero memoria richiesta per operazioni di routine

Il flusso completo da commit a produzione:

git push origin main
  → GitHub Actions build + push ghcr.io
  → Watchtower polling (intervallo configurabile)
  → Container aggiornato in produzione

Nessun SSH manuale, nessuno script da ricordare.

Scelte progettuali

Cosa ho scelto di non fare

Alcune decisioni contano quanto quelle su cosa includere.

CSS vanilla come sistema principale — architettura modulare con custom properties, Tailwind usato solo per utility sparse dove ha senso. Nessun framework CSS che guida le scelte architetturali: la struttura visiva è interamente in CSS modulare, più leggibile e più facile da debuggare.

Nessun CMS — MDX in Git è sufficiente. Aggiungere un CMS headless avrebbe significato una dipendenza esterna, un costo mensile, e complessità senza beneficio reale.

Nessun analytics di terze parti — nessun script di tracking, nessun cookie banner. Google Search Console per i dati SEO è tutto quello che serve.

Static generation over server rendering — tutte le pagine sono pre-renderizzate a build time con generateStaticParams. Il server non fa lavoro a runtime, i Time to First Byte sono minimi.

"Il criterio principale era: se tra due anni devo aggiungere un articolo, quanto ci mette? La risposta deve essere: creo un file `.mdx`, faccio push, deploy automatico in cinque minuti."
EK
Ekulos
Note di progetto