Files
blog/CLAUDE.md
T
davide e927396505 Serve the CMS admin panel under /admin on the public domain
Replace the separate CMS subdomain with path-based routing in Caddy:
/admin and /uploads go to Strapi, everything else to the frontend.
Strapi 5 nests its whole admin panel (UI and API) under /admin, so this
one prefix is enough and never collides with the frontend's own /api
routes. Drops CMS_DOMAIN and the unused STRAPI_URL env var on the cms
service; PUBLIC_STRAPI_URL now points at the same origin as the site.

Authentication is unchanged: Strapi's own admin login still gates the
panel, this only changes how it's reached.
2026-08-25 19:49:37 +02:00

6.5 KiB

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

Browser → Caddy ─┬─ /admin, /uploads → Strapi 5 → PostgreSQL
                 └─ tutto il resto   → Nuxt 4 (SSR) → REST Strapi

Caddy instrada per path, non per sottodominio: PUBLIC_DOMAIN serve sia il sito che, sotto /admin e /uploads, il pannello Strapi (Strapi 5 annida l'intera admin UI e la sua API sotto /admin, senza toccare /api). Un solo dominio, un solo certificato TLS.

  • 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 dei visitatori pubblici 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. Fanno eccezione, per costruzione: l'admin panel (/admin, uso editor/admin autenticato) e le immagini cover, che il browser carica direttamente da PUBLIC_STRAPI_URL (/uploads/..., sola lettura, nessun'autenticazione richiesta né concessa).
  • 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.

# 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 anyunknown + 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.