The site itself. A statically generated Next.js frontend where most pages ship as pure HTML with no client JavaScript unless interactivity is required. A FastAPI backend on the home server handles the dynamic features: contact form, activity feed, health monitoring, and CV downloads.
Static generation handles content; only a handful of components need client-side JavaScript. The build validates project data and checks repository URLs are reachable before generating pages, so broken links get caught before they reach production.
All project content lives in a single JSON file. No CMS, no database for content. Edit the JSON, push, and the build generates pages. Static generation via generateStaticParams creates a page for every project slug.
Build-time validation (validate-projects.ts) sends a HEAD request to every project's repository URL. If any link is dead, the build fails. This catches URL rot before it reaches production, not after a recruiter clicks a broken link.
Anti-spam without CAPTCHA. Three layers: a honeypot field (hidden input; if filled, the server returns fake success to fool bots without revealing detection), a time-based check (rejects submissions faster than 3 seconds after page load, also with fake success), and rate limiting (5 per hour per IP via slowapi). Every anti-spam response returns a 200, so bots never learn what tripped them.
On valid submission: store in SQLite, send email via ProtonMail SMTP (async, non-blocking), and send a push notification via self-hosted Ntfy. No CAPTCHA friction. Real users never notice the anti-spam; bots get a success response and think they got through.
Pulls recent commits from the Gitea API across the 10 most recently updated repos. Computes stats: commits this week, active repos, most active repo, current streak (consecutive days with commits). Private repo URLs are stripped from the response; metadata is preserved.
5-minute cache, with stale fallback up to an hour if the Gitea API is unreachable. The frontend transforms snake_case Python responses to camelCase TypeScript via a client library. Type-safe API layer with Pydantic on the backend and TypeScript interfaces on the frontend.
Path-filtered Gitea Actions. Frontend changes (anything outside backend/ and deploy/) build static HTML and push to Cloudflare Pages via Wrangler. Backend changes (backend/ or deploy/) SSH into the home server and rebuild the Docker container. Both pipelines trigger on push to main; path filters mean only the relevant service rebuilds.
The self-hosted runner and deploy target being the same machine keeps it simple: no remote SSH from a cloud CI service, no extra credentials beyond a Cloudflare API token for the frontend pipeline.