← Back to blog

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 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 Auto-Rebuild Pipeline

The interesting part is how content changes reach the live site. Strapi supports outbound webhooks that fire on publish events. A small Node.js webhook server listens for those events and does four things in sequence:

  1. Copies the latest uploaded media from Strapi into the frontend's public directory
  2. Clears the Next.js fetch cache
  3. Runs npm run build to regenerate all static pages with fresh content
  4. SCPs the output directory to the web server

The entire pipeline takes about 10–15 seconds from hitting Publish in Strapi to the live site updating.


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.

Source

The full source is on Gitea hosted internally.