A borrowed masthead, three dead names and a move to Nuxt
The first post here is about here: where the look came from, what this place was nearly called, and why it changed frameworks before anyone saw it.

Contents
Every engineering blog is tempted to make its first post about itself. I am not going to resist. A site is a small system, this one has opinions, and a few of them changed on the way here.
Starting from the bottom?
The design might resemble The Verge. Because this site was based on The Verge, duh. Not because I wanted a copy, but because that page has something most technical blogs do not: I liked it. The huge wordmark sits behind the navigation. Headlines are heavy and tight. The body drops into a serif. Mint metadata runs through a dark page, and the layout refuses to be symmetrical. (used ChatGPT for some adjectives, I think that it did well)
So I took the ideas and changed the identity. The oversized masthead is here, with a volatile easter egg (come back later to understand), the mint is here, and so is the split between loud sans-serif headlines and quiet serif paragraphs. Everything else is new, and that's what matters. The covers are from the projects, which means every story gets its own picture and I never have to go hunting for stock photos of a padlock.
Most developer blogs look like documentation. I wanted one that looks like a magazine that happens to contain code.
The sushi board
The first home page was a randomised bento grid: tiles of different sizes, rearranged on every reload, packed by an algorithm I was quite proud of. It had unit tests. It never left a gap. Then I looked at it for a while and it felt weird. Not "this doesn't belong here" weird but "I don't like asymetry all that much" type of weird.
It went. The newest story you have not read now takes a large square, and everything else sits in a plain grid of equal tiles. A few hundred lines became a few lines of CSS. Deleting that code was the best design decision on the project.
The names that did not make it
Easiest part of all software developing: naming (sarcasm). I went through a few candidates:
- the void, where return values go to die. Good mood, but edgy.
- .null, which looks great in a logo and is terrible everywhere else. Say it out loud, then try to search for it.
- nullable, which is accurate, and also a type annotation, but weird on the tongue.
All three describe absence. volatile describes something more useful. In Java it marks a field that another thread may change at any moment, so every read has to see the latest value instead of a cached copy. That is a fair summary of working in software, and it reads well in lowercase at 240 pixels tall. The name also lives in one file, in case I change my mind.
From Vue to Nuxt
The first version was plain Vue 3 with Vite and it worked well. The feature I cared about most is the infinite reader: finish a story and the next one arrives on the same page, with the URL, title and browser history following along. It picks the next part of a series first, then something unread from the same section, then anything unread. When there is nothing left it tells you so, instead of looping forever.
The problem was what a search engine saw. A client-rendered Vue app ships an almost empty HTML file and writes the page with JavaScript. I had patched in a title and description for each URL, but the article itself still only existed after the scripts ran. Some crawlers will wait for that. Others will not, and link previews usually do not.
| What a crawler receives | Vue + Vite | Nuxt, pre-rendered |
|---|---|---|
| Title and description | Yes | Yes |
| Article text | After JavaScript runs | In the HTML |
| Internal links | After JavaScript runs | In the HTML |
| Server to maintain | No | No |
A blog is the textbook case for static generation, so I moved to Nuxt and pre-rendered every page. Most components survived the trip unchanged. The work was in everything that had quietly assumed a browser.
Three things the move taught me
Reading history cannot exist on the server. It lives in localStorage, so the pre-rendered page has to pretend you have read nothing, and the real history loads after hydration. Load it earlier and the server's HTML and the browser's first render disagree.
Nuxt remounts a page when its route params change. Normally that is what you want. Here it would destroy the infinite reader every time the URL followed you into a new story, so the page gets a static key:
definePageMeta({ key: 'article-feed' })Clean URLs need checking. By default static output is a folder per page, and many hosts then redirect to a trailing slash, which disagrees with the canonical URL. Generating about.html instead of about/index.html removes the argument.
Easter egg
Can' talk about it. Find it yourself
Loading scene
A loading scene came next, and it nearly went straight back out, because a scene is the last thing you want to see. So I found a little neat trick that I will gatekeep, ihih.
What comes next
Now the hard part, which no framework helps with: writing things worth reading. If you got this far, the next story is already loading underneath this one.