Hi, I’m Max. This is the first post here, so it felt right to start with the thing you’re reading it on.
I write mostly to think. Putting a problem into words is how I find out whether I actually understand it, and a public blog keeps me honest about finishing the thought. Expect posts on whatever I’m building or breaking at the time, usually technical, not always.
People assume a personal site needs a CMS. This one is a folder of Markdown files, a static site generator and a CI pipeline. No database to patch, no admin panel to get breached, nothing to keep running between deploys. Here’s the whole thing.
The generator: Hugo
Everything lives under content/ as plain .md files. Hugo (extended edition) turns them into static HTML at build time. A new post is one command and some Markdown:
hugo new blog/my-post.md --kind blog # then write below the front matter
Front matter drives the rest: title, date, tags (the first tag becomes the category label), and description (the list teaser, search snippet and meta description, all from one field). Tag pages, RSS, pagination and the search index are generated from the content, so I never hand-edit them.
Hugo makes this almost too easy: one fast binary, no complex toolchain and it stays out of the way while I write.
The theme is the point
The look is a custom theme, not an off-the-shelf one: warm paper #f9f5ec, a single terracotta #c1522a accent, Sora for text and JetBrains Mono for code, and a // code-comment logo. One light theme, no dark-mode toggle, no runtime animation beyond a subtle scroll reveal.
Building it was the most fun part of creating this blog so far. A theme from scratch means every small decision is mine, and playing with color and type never really felt like work.
Hugo Pipes does the asset work
No Webpack, no bundler. Hugo’s own asset pipeline handles it:
- CSS and JS are fingerprinted (hashed) for cache-busting.
- Fonts are self-hosted
.woff2(variable, latin subset), so no Google Fonts request leaves the page. - Pictures are resized to responsive WebP at build time.
That’s why the site needs the extended edition: image processing and font handling depend on it.
The dynamic bits, without a backend
A static site doesn’t mean a dead one:
- Press
/on the blog page to search. It runs fully client-side, fed by an/index.jsonfile Hugo emits from the content. - Syntax highlighting is Hugo’s built-in Chroma, rendered to CSS classes (that dark terminal-card look), so there’s no highlight.js at runtime.
- The contact form currently falls back to a prefilled
mailto:. - Analytics is self-hosted, cookieless Umami, gated to production builds so it never loads while I’m previewing locally.
Deploy is a git push
Push a branch and GitLab CI builds it and deploys a preview automatically. Merge to main and the same pipeline ships to production. The build is hugo --minify, and the output is just static files. For now that goes to Vercel, though since it’s only static files I could move it somewhere self-hosted later without much fuss. Source stays private either way, and there’s no server-side runtime to keep patched.
Why bother
The payoff is that the whole site is a git repo. Content, layout, styling and deploy config are versioned together; a rollback is a git revert. There’s no CMS to upgrade and no attack surface between deploys. For something I want to still be running in ten years with zero maintenance, “a folder of text files and a build step” is hard to beat.
That is also why the boring stack matters to me. I want somewhere to think out loud that will still be here after this year’s framework is forgotten, and plain text plus a build step should manage that for a decade. Until next time.