Files
blog/CLAUDE.md
T
davide c08f53ca69 Add a language switcher with English, Italian, Spanish and French
The interface is translated through @nuxtjs/i18n: strings live in
i18n/locales/*.json, routes are prefixed except for the default English, and
useLocaleHead emits the lang attribute, the hreflang alternates and og:locale.

The switcher is a native select rather than a custom dropdown: keyboard
support, the platform picker on mobile and correct labelling come for free.

Article content stays single-language — this translates the site chrome only.
2026-08-25 14:23:09 +02:00

127 lines
6.0 KiB
Markdown

# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Interazione
- L'utente scrive in italiano. **Rispondi in italiano.**
- Codice, commenti, nomi di variabili, commit message, log e documentazione tecnica: **in inglese.**
- Nei commit **non aggiungere `Co-Authored-By: Claude`** né altri trailer di attribuzione.
## Stato
Struttura, content type, pagine blog, Docker e Caddy sono in piedi. Restano da fare:
sitemap e `robots.txt` dinamici, ricerca, e i test (nessun framework ancora configurato).
## Architettura
```text
Browser → Caddy ─┬─ dominio pubblico → Nuxt 4 (SSR) → REST Strapi
└─ sottodominio CMS → Strapi 5 → PostgreSQL
```
- Strapi è la **sola** fonte di verità editoriale. Niente altro backend (no Express/Nest/Fastify):
se serve logica server, sta in Nitro (`frontend/server/`) o in un controller Strapi.
- I visitatori pubblici non si autenticano mai. Solo editor/admin usano l'auth Strapi.
- **Il browser non parla mai con Strapi.** Le pagine chiamano gli endpoint Nitro in
`frontend/server/api/`, che sono l'unico posto dove si costruiscono query Strapi. Così
`NUXT_STRAPI_URL` resta l'indirizzo interno Docker, niente CORS e niente token nel client.
Se aggiungi una vista, aggiungi l'endpoint lì e tipizza il ritorno in `shared/types/blog.ts`.
- Il Markdown dell'articolo è convertito in HTML **nell'endpoint**, non nel componente: il
contenuto è già nell'HTML SSR e `marked` resta fuori dal bundle client.
- L'**interfaccia** è tradotta in en/it/es/fr con `@nuxtjs/i18n`: stringhe in
`frontend/i18n/locales/*.json`, rotte `prefix_except_default` (inglese senza prefisso),
`hreflang` e `og:locale` generati da `useLocaleHead()` in `app.vue`. Nessuna stringa
visibile va scritta a mano in un componente: aggiungi la chiave nei quattro file.
I **contenuti** invece restano monolingua — la localizzazione di Strapi è disattivata.
- `cms/src/index.ts` (`bootstrap`) dà al ruolo Public solo `find`/`findOne` su Article e Category,
e disattiva la registrazione pubblica: non esistono utenti front-end, solo amministratori.
Qualsiasi permesso in più va motivato.
- Postgres non è esposto pubblicamente. Media su volume persistente, mai binari nel DB.
## Comandi
Package manager: quello del lockfile. Se non esiste ancora → `npm`.
```bash
# frontend/
npm run dev # dev server
npm run build # build produzione
npm run typecheck # nuxi typecheck — obbligatorio prima di dichiarare fatto
npm run lint
# cms/
npm run develop # Strapi con content-type builder attivo
npm run build # admin panel
npm run start # produzione
# stack completo (con Caddy e domini reali)
docker compose up -d --build
docker compose logs -f cms
# stack locale senza domini né TLS: porte su localhost, niente Caddy
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build database cms frontend
```
`docker-compose.dev.yml` va passato **sempre esplicitamente**: non è un `override.yml` proprio
per non finire per sbaglio in produzione esponendo le porte.
Nessun test framework è ancora configurato. Se ne aggiungi uno, documenta qui il comando per
lanciare **un singolo test**.
## Modelli di contenuto
| Type | Campi |
|---|---|
| Article | title, slug (UID da title), content (Markdown), cover, category (rel) |
| Category | name, slug |
Deliberatamente minimale: **niente Author** (l'unico autore è l'admin), niente tag, nessun campo
SEO separato. La meta description è ricavata dall'inizio del body (`summarise` in
`frontend/server/utils/strapi.ts`), la data è il `publishedAt` di Draft & Publish, l'immagine
social è la cover. Non reintrodurre questi campi senza che servano davvero.
Draft & Publish attivo su Article. Le URL pubbliche usano lo **slug**, mai l'id numerico.
## Frontend
Rotte: `/`, `/blog`, `/blog/[slug]`, `/category/[slug]`.
- SSR o prerender per tutto ciò che è indicizzabile. Mai pagine blog client-only senza motivo scritto.
- `<script setup lang="ts">`, Composition API. Convenzioni Nuxt standard (`pages/`, `components/`,
`composables/`, `layouts/`, `server/`).
- Un solo punto di accesso a Strapi: un composable/util tipizzato. Non sparpagliare `$fetch` nelle pagine.
- Tipizza esplicitamente il confine API. Niente `any``unknown` + narrowing.
- Query Strapi: richiedi solo i campi e le relazioni che servono (`fields`, `populate` mirati).
Gestisci sempre 404, lista vuota, errore API.
- Ogni articolo indicizzabile: title unico, meta description, canonical, Open Graph, JSON-LD
`BlogPosting`, gerarchia heading semantica. Il contenuto deve esistere nell'HTML server-rendered.
- Accessibilità non negoziabile: focus visibile, input etichettati, alt significativi, link
descrittivi, navigazione da tastiera.
Il riferimento Hostinger è solo ispirazione visiva. Non copiare codice o asset.
## Priorità
Correttezza e integrità dati → sicurezza → semplicità → SEO/a11y → performance.
Nessuna dipendenza, astrazione o servizio senza un bisogno concreto e attuale. Preferisci i
built-in Strapi al reimplementare funzioni CMS in Nuxt.
## Vincoli
- Mai committare `.env`, segreti, token, credenziali, chiavi. Mantieni `.env.example` sanificato.
- Mai hard-codare domini di produzione, URL privilegiati o credenziali. Vanno in env var.
- Non indebolire auth, CORS, TLS o security header per comodità. Least privilege sui ruoli Strapi.
- Richiedono **approvazione esplicita**: operazioni distruttive, migrazioni irreversibili, modifiche
a dati o configurazione di produzione, cambi di credenziali.
- Non riformattare file non correlati, non fare refactor collaterali, non riscrivere la history,
non force-push.
- Le decisioni architetturali di questo file non si cambiano in silenzio: spiega il trade-off prima.
## Prima di dichiarare completo
Lint → test → typecheck → build dell'app toccata, con gli script del `package.json` relativo.
Non affermare che un check è passato se non l'hai eseguito. Chiudi riassumendo cosa è cambiato e
quali rischi restano aperti.