This Website
The site you're reading right now: statically generated Next.js 16 with MDX content, aurora-grade animations, and a Docker + Nginx + Certbot pipeline that deploys itself.
The brief I gave myself#
Build a personal site that stays maintainable in five years: content lives as MDX in Git, pages render statically, animations impress without turning the bundle into a landfill, and deployment is one git push. No CMS, no database, no server babysitting.
Architecture#
- Next.js 16 App Router with static generation for every page. Articles and project case studies are
.mdxfiles undersrc/content/, read at build time. - MDX pipeline —
@next/mdxwithremark-gfm,rehype-slugandrehype-pretty-codefor syntax-highlighted, anchor-linked articles. - Counting-free components: the aurora hero, split-text headings, spotlight cards and marquee are adapted from React Bits building blocks, tuned to share one accent palette.
- Route handlers for the only dynamic bit: a minimal health check for the container (the contact page is deliberately static and hands off to the visitor's mail client — no form endpoint, no stored messages).
The deployment pipeline#
git push → GitHub Actions
├─ build & push Docker image to GHCR (cached)
└─ ssh to server → docker compose pull && up -d
└─ Nginx reverse proxy + Certbot SSLThe server runs two containers — the Next.js standalone image and Nginx — plus a Certbot renewal timer. Nothing else. The whole footprint fits in a small VM beside the family server it shares.
Details I refused to skip#
- Real metadata. Per-page titles, canonical URLs, Open Graph cards rendered at build time, an RSS feed and a sitemap — SEO hygiene, done once.
- Content/code separation. Publishing a post means adding one
.mdxfile; changing a design means touching components. The two never mix. - Performance as respect. Fonts subset, animations respect
prefers-reduced-motion, and WebGL backdrops are scoped so the writing stays readable.
What's next#
A uses page for the tools I actually use, per-post social cards, and — when a feature genuinely needs state — a tiny SQLite read model behind the route handlers. Not before.