Colophon

This page describes how the site you are reading actually works. It replaces an older description that had quietly gone out of date, which is a fair argument for keeping this one honest.

The short version

There is no database and no server rendering pages. Every page here was generated once, as a file, and is served as that file. The repository is the only source of truth: if something is not committed, it does not exist.

How a change travels

1. Writing. Content is markdown in content/, one file per page or post, with a small block of frontmatter for the title, date, tags and language. content/site.yaml holds the things that are not any single page’s business: the navigation, the palette, the languages.

2. Editing. There is an admin interface, but it is not a CMS in the usual sense — it holds no content of its own. It reads and writes files through the GitLab API and commits them. It never writes HTML, and it has no database behind it. Close the admin and everything you wrote is a commit like any other, reviewable in a diff. Saving is a deliberate button press rather than an autosave, because every save costs a build.

3. Building. A commit triggers GitLab CI, which runs a static site generator written for this site. It is under two thousand lines of plain Node.js with five dependencies — markdown, YAML, frontmatter, templates and syntax highlighting — and no framework. It reads content/, validates it, and writes plain HTML into dist/.

Validation is strict on purpose. A missing title, a duplicate URL, an unknown template, a tag that cannot become a clean URL, a broken redirect: each of these fails the build with the file and line, rather than publishing something subtly wrong.

The output is deterministic. Two builds of the same commit produce byte-identical files — no timestamps, no unordered iteration, stable hashes. That means a deploy diff only ever shows real changes.

4. Serving. The finished dist/ is deployed to a small server I run at home, by a worker that lives on that same box — it pulls each build from GitLab and swaps it into place, so nothing ever connects inward to deploy. The site is reached through a Cloudflare Tunnel: Cloudflare still answers first and caches the result, but the files come from my hardware. Syntax highlighting is baked in at build time, so no highlighting library is ever sent to your browser. A page here ships essentially no JavaScript — three small, deliberate exceptions: search, the contact form, and the one line in the footer that asks the server how long it has been up. The full story of that move is on its own page — running on my own hardware.

5. The dynamic parts. A small Node.js service on the same box handles the two things a static file cannot: the contact form and the admin API, both on this origin under /api/. It holds every secret; nothing sensitive reaches the browser, and the contact form sends its mail out over authenticated SMTP. The admin is additionally behind Cloudflare Access, so it is not merely obscure but actually closed — that check happens at Cloudflare’s edge, before the request is ever handed down the tunnel.

Two languages

The site is served in English at the root and in Greek under /el/. Each document declares its own language and gets the right lang attribute, its own feed and its own search index; tag listings never mix the two. Interface strings live in per-language files, and a missing or malformed translation fails the build rather than falling back silently to English.

Type

Headings are set in GFS Neohellenic, released by the Greek Font Society under the SIL Open Font Licence. Almost every webfont treats Greek as an afterthought bolted onto a Latin design; this one was drawn the other way round, which matters on a site where half the pages are Greek. It is served from this domain, not a font CDN, and subsetted from the full 450 KB families down to about 16 KB per weight — Latin, Greek, and the punctuation actually in use. Body text stays on whatever sans your system already has, so there is nothing more to download for it.

The 2026 migration

This domain used to be a one-page card, with nine years of blog at blog.meglis.com — WordPress until 2022, Jekyll after that. In August 2026 the two were merged into what you are reading: 143 posts converted to markdown, their images brought along, and every old URL kept working through a redirect map that is verified at build time.

The conversion was done by a purpose-written converter that refuses to continue when it meets markup it has not been taught, rather than dropping it quietly. Every converted post was then diffed word-by-word against the original. None lost a word.

A month later, in September 2026, the site moved off rented infrastructure entirely and onto a server I run at home, without changing a single published byte — the same files, served from my own hardware behind a Cloudflare Tunnel. That is a story in itself: running on my own hardware.

Testing

Roughly three hundred tests, in layers that catch different things: the generator’s output and validation rules, the Worker’s API behaviour, browser tests of the flows a person performs, visual regression against committed screenshots, and an accessibility scan that holds every page to WCAG AA. The accessibility suite has no exclusions, which occasionally means losing an argument with it about a colour.

What it costs

Almost nothing, and now in a different currency. GitLab hosts the repository and runs the build on its free tier; Cloudflare provides the tunnel, the CDN, TLS and Access, also free. What changed with the move home is that serving the site now costs a few watts on a machine that was already running, rather than nothing at all on a rented one. The old free-tier constraint — budgeted CI minutes — still shapes the build, which takes about twenty seconds; the new constraint is simply that a home server has to stay alive.

Source

Generator, Worker, admin interface, infrastructure scripts and content all live in one repository, which is private for now. Other things of mine are at gitlab.com/sapemeg.