What is this?
This portfolio itself. A statically generated website built with Astro that showcases my projects and blog posts. No CMS, no framework overhead — just MDX files compiled to HTML at build time.
The site runs on a Hetzner VPS behind Nginx and WireGuard VPN , with automated deployment via GitHub Actions . During development, it is only accessible via VPN.
Architecture
- Static Site Generation with Astro 5 — all pages are generated at build time. No Node.js process on the server, no database access, minimal attack surface.
- Content Collections for blog and projects — type-safe frontmatter with Zod validation, MDX for interactive components like glossary references.
- Glossary system — centralized definitions in TypeScript, referenced via the
<Term>component in every article. Desktop: sidebar, mobile: dialog. - Floating toolbar — theme toggle (dark/light), accent color picker (HSL ring), glossary button. Expands on click, closes on click outside.
- QR code generator — interactive tool at
/qr-generator, running entirely client-side.
Infrastructure
Two Hetzner servers connected via a private network (10.0.0.0/16):
- Portfolio server — Nginx as Reverse Proxy , Let’s Encrypt for TLS, serves static files from
/var/www/mathis-adler.dev/dist/ - WireGuard server — VPN endpoint for split-tunnel access, dnsmasq for DNS overrides within the VPN
The separation into two servers isolates the VPN service from the web server. Clients on the VPN reach services via internal IPs, while public access is controlled by Nginx.
Deployment
Push to main triggers a GitHub Actions workflow:
npm ci && npm run build— Astro compiles all MDX files to static HTMLrsync --delete dist/— only changed files are transferred- Nginx serves the new files immediately — no restart needed
What I Learned
- Astro’s Content Collections with Zod validation catch frontmatter errors at build time — no “undefined” in production.
- MDX allows Astro components in Markdown. The glossary system would be significantly more cumbersome without
<Term>as an MDX component. - A static portfolio has virtually no attack surface. The security work is in the infrastructure (Nginx hardening, VPN, SSH), not in the application code.