I didn't want to moderate comments through an admin panel: opening the CMS studio for a single button is more hassle than the task is worth. New comments arrive by email with an "Approve" button. The link behind it is signed with HMAC-SHA256 over the document ID using a write key; the endpoint validates the signature via timingSafeEqual and patches the record. Commenters don't provide an email, just a handle they pick themselves.
The project is built on a specific trade-off: an external service versus a couple dozen lines of custom code. The site runs on my VPS: server-rendered Nuxt 4 inside Docker, with push-to-deploy on main. The pipeline builds the image, pushes it to a registry, and restarts the container—around three minutes from commit to live site. Analytics is Umami in the same compose setup, cookieless and without a consent banner. Fonts are hosted locally, with zero third-party scripts on the page. Cloudflare handles DNS only.
Content—portfolio work, posts, and settings like availability status—lives in Sanity and is fetched via server routes. The API token never reaches the browser. Under the hood, the blog runs on a custom publishing engine: bilingual support, table of contents generated from markup on render, a reading progress bar, sources listed below articles, and comments. The site generates its own social preview cards: Satori builds the SVG, resvg converts it to PNG, with fonts stored on the server. The site is machine-readable as well: llms-full.txt is compiled on demand from the CMS, and the contact form is registered as an agent tool.
Security includes HSTS with preload, COOP, framing prevention, and a Permissions-Policy disabling camera, microphone, and geolocation. CSP runs in Report-Only mode: the allowlist is configured, violations arrive via email, but it's not set to block yet—I need to make sure the allowlist is exhaustive first. Image dimensions are explicitly populated from asset metadata, eliminating layout shifts. AVIF was dropped from the pipeline: CDN transformations occasionally failed to warm up in time, and <picture> fallbacks broke during SSR. Testing is lean—two unit tests for composables and an end-to-end run across the pages.