How This Site Is Built
A static site with a headless CMS backend, an auto-rebuild pipeline, and no hosting fees beyond a single VPS.
Most personal sites are either fully static with no CMS, or they run a database-backed monolith like WordPress. This site takes a middle path: a headless CMS for content management and a fully static frontend that gets rebuilt automatically whenever something is published.
The full path from a content edit or code push to a live visitor request.
The Stack
The backend is Strapi v5, an open-source headless CMS running on a self-hosted VPS managed by PM2. All content — blog posts, projects, resume data — lives in Strapi and is exposed via a REST API. The frontend is Next.js 15 with output: 'export', which means the entire site is pre-rendered to plain HTML, CSS, and JavaScript at build time. There is no Node.js server running in production. Nginx serves the static files directly.
The Deploy Pipeline
An earlier version of this pipeline used a webhook that rebuilt the frontend and shipped it to production automatically within seconds of hitting Publish in Strapi. That's been retired in favor of an explicit, two-stage process — a little less instant, but it removes a real failure mode: a build that accidentally pointed at internal-only settings could previously reach production with nobody reviewing it first.
- A manually-triggered CI workflow refreshes the backend and the staging environment with the latest code, then the frontend is rebuilt and checked on a staging subdomain.
- A second, separately-triggered workflow does an isolated production build, scans the output for anything that would leak an internal-only hostname, and only then ships it to the public VPS.
Nothing reaches the public site automatically — every release is a deliberate, two-step action, trading a few minutes of hands-on time for the guarantee that an internal URL can never quietly end up in front of real visitors.
Why Static Export
A static site has no attack surface beyond what Nginx exposes, scales trivially, and survives traffic spikes without breaking a sweat. The tradeoff is that content changes require a rebuild — acceptable for a personal site where real-time updates aren't needed.
DNS
DNS for the domain is hosted on Cloudflare, used purely for nameserver hosting — there's no proxying or CDN caching sitting in front of the origin. Requests go straight to the VPS, so Nginx sees real visitor IPs and there's no cache to purge after a deploy.