Strapi + Postgres are gone in favor of PocketBase: a single Go binary with embedded SQLite, built-in admin UI and per-collection API rules. No content existed yet, so this is a clean swap with no data migration. Collections and rules are defined as code in pocketbase/pb_migrations/ and applied automatically on first boot. Draft & Publish has no native PocketBase equivalent, so it's reproduced with a nullable `publishedAt` field enforced by listRule/viewRule, matching the old Strapi semantics. Routing flips: PocketBase's admin UI and REST/file API are hardwired to `/_/` and `/api/*` at the domain root (its own dashboard assets and API calls reference those paths directly, so a stripped path prefix like `/admin/*` would break them). `/api` is therefore reserved for PocketBase now, and the frontend's Nitro endpoints move to `/content/*` (frontend/server/routes/content/, not server/api/). A `/admin` vanity route in Nitro (not Caddy) redirects to `/_/`, so it works the same in dev, where Caddy isn't part of the stack, and in production. frontend/server/utils/strapi.ts becomes pocketbase.ts; queries.ts is rewritten for PocketBase's filter/sort/fields/expand query syntax. StrapiImage becomes MediaImage (no width/height — PocketBase file fields don't store dimensions, and the cover images already reserve their aspect ratio via CSS, so this is not a regression). docs/*.md, CLAUDE.md and README.md are updated in the same commit.
8.3 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: Claudené 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 ─┬─ /_/* e /api/* → PocketBase (admin UI + API)
└─ tutto il resto → Nuxt 4 (SSR) → REST PocketBase (interno)
("/admin" fa redirect a /_/, gestito da Nitro)
Caddy instrada per path, non per sottodominio o porta: PUBLIC_DOMAIN serve sia il sito
che il pannello PocketBase. /_/* (dashboard) e /api/* (REST/file API) vanno senza prefisso
a PocketBase — la sua dashboard referenzia se stessa con quei path assoluti, quindi non si possono
instradare con uno strip-prefix (es. /admin/* riscritto): romperebbe gli asset/le chiamate della
dashboard. Per questo /api è riservato a PocketBase, non a Nitro: gli endpoint del frontend
vivono sotto /content/* (frontend/server/routes/content/, non server/api/). /admin è una
route Nitro (frontend/server/routes/admin.get.ts) che fa redirect a /_/, non una regola
Caddy: così funziona identico anche in sviluppo, dove Caddy non fa parte dello stack. Un solo
dominio, un solo certificato TLS. Se cambi questa scelta di routing, spiega il trade-off prima
(vedi Vincoli).
- PocketBase è la sola fonte di verità editoriale (CMS + database SQLite in un solo processo).
Niente altro backend (no Express/Nest/Fastify): se serve logica server, sta in Nitro
(
frontend/server/) o in una regola/migration PocketBase. - I visitatori pubblici non si autenticano mai. Solo il superuser usa l'auth PocketBase.
- Il browser dei visitatori pubblici non parla mai con PocketBase per i contenuti. Le pagine
chiamano gli endpoint Nitro in
frontend/server/routes/content/, che sono l'unico posto dove si costruiscono query PocketBase. CosìNUXT_POCKETBASE_URLresta l'indirizzo interno Docker, niente CORS e niente token nel client. Se aggiungi una vista, aggiungi l'endpoint lì (sotto/content/*, mai/api/*) e tipizza il ritorno inshared/types/blog.ts. Fanno eccezione, per costruzione: l'admin panel (/_/, uso superuser autenticato, raggiungibile anche da/admin) e le immagini cover, che il browser carica direttamente daPUBLIC_POCKETBASE_URL(/api/files/..., 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
markedresta fuori dal bundle client. - L'interfaccia è solo in italiano, stringhe statiche nei componenti (niente
@nuxtjs/i18n, niente file di traduzione):<html lang="it">è fisso innuxt.config.ts. La traduzione per i visitatori stranieri è delegata all'estensione Google Translate del browser, non è gestita dall'app. I contenuti restano monolingua. pocketbase/pb_migrations/*.jsdefinisce collection e regole:listRule/viewRulepubblici solo suarticles(solo pubblicati, viapublishedAt) ecategories;createRule/updateRule/deleteRulesemprenull(solo superuser). Qualsiasi permesso in più va motivato.- SQLite (PocketBase) non è esposto pubblicamente sulla rete — solo tramite l'API PocketBase
stessa. Media sul volume persistente
pocketbase-data, mai binari fuori da lì.
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
# pocketbase/ (nessun npm script: binario singolo, le migration si applicano da sole all'avvio)
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build pocketbase frontend
# stack completo (con Caddy e domini reali)
docker compose up -d --build
docker compose logs -f pocketbase
# 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 pocketbase 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, content (Markdown), cover, coverAlt, category (rel), publishedAt, authorName |
| Category | name, slug |
Deliberatamente minimale: niente tag, nessun campo SEO separato. La meta description è ricavata
dall'inizio del body (summarise in frontend/server/utils/pocketbase.ts), la data è
publishedAt, l'immagine social è la cover. Non reintrodurre campi senza che servano davvero.
PocketBase non ha Draft & Publish nativo: publishedAt vuoto = bozza, valorizzato (e non nel
futuro) = pubblicato, imposto dalla listRule/viewRule della collection articles, non da
codice applicativo. Le URL pubbliche usano lo slug, mai l'id del record.
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 PocketBase: un composable/util tipizzato. Non sparpagliare
$fetchnelle pagine. - Tipizza esplicitamente il confine API. Niente
any—unknown+ narrowing. - Query PocketBase: richiedi solo i campi e le relazioni che servono (
fields,expandmirati). 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.
Documentazione
docs/ descrive struttura e comportamento del repo (architettura, content model, frontend) a un
livello più approfondito di questo file. Se implementi nuove funzionalità, endpoint, content type
o cambi l'architettura (nuovo servizio, nuova rotta Caddy, nuovo modo di scambiare dati tra
frontend e CMS), aggiorna il file docs/*.md pertinente nello stesso commit, o creane uno
nuovo se non esiste una sezione adatta. Non lasciare docs/ disallineata col codice.
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 PocketBase (regole per-collection, migration) al reimplementare funzioni CMS in Nuxt.
Vincoli
- Mai committare
.env, segreti, token, credenziali, chiavi. Mantieni.env.examplesanificato. - 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 sulle regole PocketBase.
- 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. Se hai toccato architettura o
funzionalità, verifica di aver aggiornato docs/ (vedi Documentazione). Chiudi
riassumendo cosa è cambiato e quali rischi restano aperti.