Sito personale di Matteo Carola — Software Engineer | Cloud & AI Engineering | AWS. One-page statica, veloce e accessibile. Il repository è esso stesso parte del portfolio.
Live: matteocarola.com
- Stack e scelte tecniche
- Avvio rapido
- Comandi
- Struttura del progetto
- Dove si modificano i contenuti
- Tema chiaro/scuro
- Internazionalizzazione (i18n)
- Blog (predisposto)
- Tech Radar (pipeline semi-automatica)
- SEO
- Accessibilità e performance
- Asset generati
- CI
- Deploy
- Riservatezza dei contenuti
- Licenza
| Tecnologia | Perché |
|---|---|
| Astro 5 | Sito statico che spedisce zero JavaScript di default: l'unico JS in pagina è il toggle del tema (~1 KB inline). Content collections native per il futuro blog e i18n routing integrato. |
| TypeScript (strict) | astro/tsconfigs/strict: contenuti e componenti tipizzati end-to-end. |
| Tailwind CSS 4 | Via @tailwindcss/vite. I design token del tema sono CSS custom properties (--bg, --ink, --accent, …) mappate su utility con @theme inline. |
| @fontsource-variable | Inter + Space Grotesk self-hosted: nessuna richiesta a CDN esterne, niente layout shift da font remoti. |
Perché non Next.js? Per una one-page statica non servono SSR, server actions o runtime client: Astro produce HTML puro e ottiene punteggi Lighthouse alti per costruzione, non per ottimizzazione a posteriori.
Requisiti: Node.js ≥ 20 (consigliata la 22 LTS) e npm.
git clone https://github.com/Matteo0421/matteocarola-dev.git
cd matteocarola-dev
npm install
npm run dev # → http://localhost:4321| Comando | Effetto |
|---|---|
npm run dev |
Server di sviluppo su localhost:4321 con HMR |
npm run build |
Build di produzione in dist/ (include la sitemap) |
npm run preview |
Serve la build di produzione in locale |
npm run check |
Typecheck di .astro e .ts (astro check) |
npm run lint |
ESLint (flat config, typescript-eslint + eslint-plugin-astro) |
npm run format |
Prettier su tutto il repo (plugin Astro + ordinamento classi Tailwind) |
npm run format:check |
Verifica formattazione senza scrivere (usato in CI) |
├── .github/workflows/
│ ├── ci.yml # CI: typecheck, lint, format, build
│ └── radar.yml # Tech Radar: cron → card proposte → PR
├── public/ # asset statici serviti as-is
│ ├── favicon.svg # monogramma, si adatta a light/dark
│ └── og.png # immagine Open Graph 1200×630
├── scripts/
│ ├── generate-og.ps1 # rigenera og.png e apple-touch-icon.png
│ └── radar/generate.mjs # generatore delle card del Tech Radar
└── src/
├── components/ # una sezione = un componente .astro
├── content.config.ts # collection: blog (predisposta) + radar
├── content/blog/ # futuri post in Markdown
├── content/radar/ # card del Tech Radar (pubblicate via PR)
├── data/ # ★ contenuti tipizzati (unica fonte)
│ ├── profile.ts # anagrafica pubblica, about, contatti
│ ├── skills.ts # competenze raggruppate
│ ├── projects.ts # progetti in evidenza
│ └── experience.ts # esperienza e formazione
├── i18n/ui.ts # ★ stringhe UI (predisposto per l'inglese)
├── layouts/Base.astro # <html>, tema no-flash, SEO
├── pages/
│ ├── index.astro # composizione della one-page
│ ├── radar.astro # pagina /radar (Tech Radar)
│ └── robots.txt.ts # robots.txt generato dalla config `site`
├── styles/global.css # design token + Tailwind
└── types.ts # tipi condivisi dei contenuti
I testi non vivono nei componenti: sono dati tipizzati.
- Contenuti (progetti, esperienza, competenze, bio) →
src/data/*.ts. Il compilatore segnala subito un campo mancante o sbagliato. - Stringhe UI (navigazione, etichette, meta tag) →
src/i18n/ui.ts.
Esempio: per aggiungere un progetto basta un nuovo oggetto Project in src/data/projects.ts — nessun markup da toccare.
- Il tema è
data-theme="light|dark"su<html>; i token inglobal.csscambiano di conseguenza (:root/[data-theme='dark']). - Uno script inline nel
<head>leggelocalStorage(fallback:prefers-color-scheme) prima del primo paint: nessun flash del tema sbagliato. - Il toggle nell'header persiste la scelta e aggiorna
aria-pressed. - La variante Tailwind
dark:è ridefinita con@custom-variantper seguiredata-themeinvece del solo media query.
Il sito è in italiano, ma è predisposto per l'inglese:
- i testi UI sono già centralizzati in
src/i18n/ui.tscon un tipoDictionary; - per l'inglese: aggiungere
export const en: Dictionary, tradurre i dati insrc/data/(es. campi per locale) e attivare l'i18n routing di Astro (defaultLocale: 'it',locales: ['it', 'en']) con le pagine sottosrc/pages/en/.
La collection blog è già definita in src/content.config.ts (loader glob, schema Zod con title, description, pubDate, lang, draft). Per attivare il blog:
- creare un post:
src/content/blog/primo-post.mdcon il frontmatter dello schema; - creare
src/pages/blog/index.astro(elenco) esrc/pages/blog/[id].astro(dettaglio) usandogetCollection('blog'); - la sitemap si aggiorna da sola alla build.
La pagina /radar pubblica card brevi su novità cloud/AWS/AI. Le card non le scrivo da zero: le propone una pipeline, io le approvo. È un piccolo progetto Cloud & AI end-to-end, tutto in questo repo e tutto a costo zero.
GitHub Actions (cron, 2×/settimana)
└─ scripts/radar/generate.mjs (Node 22, zero dipendenze npm)
├─ 1. fetch delle fonti AWS What's New (RSS) · Hacker News (API Algolia)
│ · GitHub Search (repo nuovi in crescita)
├─ 2. dedup per URL contro le card già in src/content/radar/
├─ 3. Gemini (free tier) sceglie ≤3 novità e scrive titolo, sintesi
│ e tag in italiano (output JSON con schema)
└─ 4. card in Markdown → Pull Request → review umana → merge
└─ Vercel pubblica
Scelte deliberate:
- Umano nel loop: la pipeline apre una PR, non pubblica mai da sola. In review posso modificare la sintesi, aggiungere una nota personale (il body del file
.md) o scartare la card. - Zero dipendenze: lo script usa solo la
fetchnativa di Node — niente supply chain da mantenere per un cron. - Anti-allucinazione: il modello sceglie da una lista numerata e scrive solo a partire da titolo+contesto forniti; URL e fonte restano quelli originali, mai generati.
- Costo zero: GitHub Actions (repo pubblico), API gratuite, Gemini free tier (volume: pochi item a settimana).
Per eseguirlo in locale: node scripts/radar/generate.mjs --dry-run (nessuna API key richiesta, stampa i candidati). Il run completo richiede GEMINI_API_KEY; in CI la legge dai secrets del repo.
Limite noto (accettato per semplicità): la dedup guarda solo le card pubblicate su main, quindi una card scartata chiudendo la PR può essere riproposta finché la notizia resta nelle fonti.
- Meta
title/description, canonical, Open Graph e Twitter card inSeo.astro - JSON-LD
Person(schema.org) - Sitemap generata da
@astrojs/sitemap(sitemap-index.xml) e referenziata da unrobots.txtcostruito alla build sulla configsite(endpoint) og.png1200×630 per le anteprime social
- HTML semantico: landmark, un solo
h1, gerarchia dei titoli,aria-labelsu link/pulsanti icona - Skip link "Salta al contenuto", focus ring visibile,
prefers-reduced-motionrispettato - Contrasto AA verificato su entrambi i temi
- Font self-hosted (woff2 variabili), niente richieste esterne, niente cookie né tracker
- Target: Lighthouse ≥ 95 su tutte le categorie
public/og.png e public/apple-touch-icon.png sono generati da scripts/generate-og.ps1 (GDI+, solo Windows) e committati: la build non dipende dallo script.
powershell -ExecutionPolicy Bypass -File scripts/generate-og.ps1Ogni push/PR su main esegue: astro check → eslint → prettier --check → astro build (workflow).
Deploy su Vercel (piano hobby, gratuito), in due fasi.
Fase 1 — online senza dominio (attuale)
- push del repo su GitHub;
- su vercel.com → Add New… → Project → importare
matteocarola-dev: Astro viene riconosciuto da solo (buildastro build, outputdist/); - il sito è servito su
https://matteocarola-dev.vercel.app.
Fase 2 — dominio custom (quando si vuole)
- acquistare
matteocarola.dev(registrar consigliati: Cloudflare Registrar o Porkbun, ~10–13 €/anno a listino); - Vercel → Settings → Domains → aggiungere il dominio e seguire le istruzioni DNS;
- aggiornare
siteinastro.config.ts: è l'unico punto da toccare — canonical, Open Graph, sitemap e robots.txt derivano tutti da lì.
Nota sul TLD .dev: è in HSTS preload, funziona solo in HTTPS — su Vercel il certificato è automatico.
Il sito è pubblico. Regole applicate ai contenuti:
- contatti limitati a email, LinkedIn e GitHub — niente telefono né altri dati personali;
- i progetti per clienti sono raccontati ad "altitudine CV": scopo, ruolo e concetti (GenAI, RAG, IaC), senza numeri interni, dettagli hardware o prodotti specifici attribuiti a un cliente;
- nomi di tecnologie solo nella sezione Competenze, non attribuita.
Checklist completa in src/data/projects.ts (commento in testa al file).
Codice sotto licenza MIT. I testi e i contenuti personali (bio, progetti, immagini) sono © Matteo Carola — riusali solo con permesso.