How this site got built (and why it's just HTML)
A few people have asked me what is running under the hood here, so this is the post about the post. Short version: nothing is running. It is a folder of HTML files and one CSS file, served as-is. No framework, no build step, no JavaScript, no backend.
I am not a web developer
Should probably get this out of the way up front. I am a security
person, not a web dev. I do not write frontend code for a living, I do
not have strong opinions about CSS frameworks, and I cannot remember
the last time I voluntarily wrote a div. So a lot of the
actual building, the styling, the layout, getting things to look
halfway decent on a phone, was done with a pretty heavy assist from
Claude Code.
The honest reason is that I wanted to ship something instead of keep thinking about shipping something, and the whole "vibe coding with an LLM" thing has been trending hard enough that I wanted to actually try the workflow on something real but low-stakes. Worst case the site looks bad and three people see it. There is no production traffic to break, no users whose data I can leak, no compliance auditor to upset.
The stack being this dumb-simple is part of why this worked. With plain HTML and one CSS file there is not much to get wrong, the model cannot hallucinate a framework I am not using, and I can read every line in the repo and actually understand it. If I had let it scaffold a Next.js project with twelve dependencies I would not be able to tell a real bug from a vibe-coded landmine. Same reasoning as the security argument below, just from a different angle: small, readable, boring.
The stack, such as it is
Every page on this site is a standalone .html file. The
header and footer are copy-pasted into each one. Posts live in
/posts as their own files. The post index on
posts.html is a hardcoded <ul> that I
edit by hand whenever I publish something. Styling is one
style.css at the root, written by me, short enough that I
can keep the whole thing in my head.
That is the whole thing. There is no static site generator, no template engine, no markdown pipeline, no content management system.
Why so basic
Honestly, mostly because I wanted to focus on writing instead of tooling. But the security angle is real too, and since this is ostensibly a security blog, I might as well lean into it.
No JavaScript means basically no XSS surface. Nothing on this site executes user input, nothing is dynamic, nothing pulls third-party scripts that could get supply-chain poisoned the next time some maintainer's npm token leaks. The browser renders text. That is the entire interaction model.
No backend means nothing to compromise. No database to leak, no auth to bypass, no admin panel to brute force, no file upload endpoint, no WordPress plugin from 2014 sitting in a corner with an unauthenticated RCE waiting to be found by the next botnet that crawls past. If you remove the server, you remove most of the vulnerability classes that show up on personal blogs.
No package manager means no dependency tree. I do
not have a package.json with 1,200 transitive dependencies,
one of which is going to ship a postinstall script that exfiltrates my
env vars in six months. I do not get GitHub Dependabot alerts about my
blog. There is nothing to update because there is nothing installed.
My realistic threat model for this site is "someone gets push access to the repo." That is it. If my GitHub account is compromised, I have much bigger problems than my blog getting defaced, and the fix is the same either way.
Hosting
It is hosted on Cloudflare Pages,
served from victorcasteur.be. The source lives in a
GitHub repo, I push to main, Cloudflare picks up the
change and serves the files over HTTPS from their edge network a
minute or two later. No server I have to patch, no TLS cert I have
to renew, no cron job slowly rotting in some VPS I forgot I was
paying for. If Cloudflare goes down, a non-trivial chunk of the
internet is going down with it, and my blog being unreachable is
not going to be the headline.
The setup on the Cloudflare side is also basically nothing. Point a Pages project at the repo, leave the build command empty (there is no build), set the custom domain in the dashboard, let Cloudflare handle the DNS and the cert. The deploy is "git push."
I started out on GitHub Pages, which works fine and is the obvious
default for a repo named username.github.io. I moved to
Cloudflare Pages mostly so I could put the site on my own domain
without faffing around with a CNAME file and waiting
for DNS to converge. Same idea, slightly different paint job, and
the deploy story is identical.
The Jekyll detour
I did not actually start here. The first version of this site was a Jekyll setup, because Jekyll is the default thing GitHub Pages suggests and I figured I should use the proper tool. I never wrote a single real post in it. The friction of the build/preview loop, the Gemfile, the front matter, the layouts, all of that turned out to be more activation energy than I had for what amounts to a few static pages a year.
So I deleted it and wrote the HTML by hand. The old Jekyll project
is still in the repo under _jekyll_archive/, which the
leading underscore tells GitHub Pages to ignore. I left it there as a
small monument to wasted effort and a reminder that picking the
"proper" tool is not free.
The trade-offs
This setup is not magic. Adding a new post means editing two files:
the post itself, and posts.html to add it to the index.
If I ever change the header or the footer, I have to do it in every
file. If I had hundreds of posts this would be painful. I do not have
hundreds of posts. The day I do, I will reconsider, and probably
regret it.
I also do not have search, comments, analytics, or RSS. I do not especially want any of those. If you want to talk about a post, my email is in the footer.
Closing
Every time I open a personal blog and watch it download four megabytes of JavaScript to render three paragraphs of text, I get a little more convinced this was the right call. Boring is a feature. Plain HTML loads instantly on bad coffee shop wifi, works without JavaScript, and works in lynx if you are weird like that. The attack surface really is just "static text the browser parses," which is about as small as it gets on the modern web.
If you are thinking about putting up a personal site, consider just writing the HTML. It is fine.