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.
The interface was translated in en/it/es/fr via @nuxtjs/i18n. Drop the
module, the locale files, and the language switcher: the UI is now
static Italian text, with translation for foreign visitors left to the
Google Translate browser extension instead of the app.
Strapi already records which admin user created an entry, so the byline needs
no extra field to fill in: the Article content type sets populateCreatorFields
and the API layer exposes only that user's firstname and lastname. The name
also feeds the BlogPosting structured data.
Also fixes the gap below the article header, which until now came from the
cover image's margin and therefore vanished on articles without a cover,
leaving the body text against the byline.
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.
The browser never talks to Strapi: pages call Nitro endpoints under
server/api/, which are the only place Strapi queries are built. That keeps the
Strapi URL internal to the Docker network and avoids CORS entirely.
Article bodies are rendered from Markdown to HTML in the endpoint, so the
content is server-rendered and crawlable, marked stays out of the client
bundle, and the meta description is derived from the opening of the body.
Pages carry canonical URLs, Open Graph tags and BlogPosting structured data,
and handle empty, missing and failed states.