How to make a blog if you're a s&box dev

Right

It felt right to kick off the blog with a post about actually making the blog itself. How do you get your own website up and running as painlessly as possible, when your background is s&box development? Let's find out.

How to make a website

If you've done any programming before, you probably have a rough idea of how this works. Beyond the boring stuff like buying a domain and setting up DNS, you need a server somewhere that serves pages to the browser:

  • if it's a modern SPA, HTML is generated on the fly — either in the browser or on the server;
  • if it's something simpler, the server generates an HTML file on each request;
  • and if it's even simpler than that, there's just a static HTML file sitting on the server, returned as-is for the requested URL.

A SPA is overkill here — a blog doesn't need much in the way of user interaction. One step back from that lands us in PHP territory, where the backend just generates HTML and sends it to the browser. But that still means writing and maintaining a backend, dealing with security, and paying for a server. See where I'm going with this?

Right — the easiest way to eliminate backend headaches is to eliminate the backend entirely, and just send users pre-built HTML. Static sites cut out all of that complexity. What clicked for me was realizing that static sites don't require hand-writing markup like it's the Web 1.0 era — you can still use modern tooling.

Static Site Generation

Nothing stops us from using modern frameworks while still getting all the benefits of static sites. I first came across this with Svelte, but most frameworks have some form of SSG support. The idea is simple:

  1. Build the site as if it were a regular server-side app, just without user interactivity.
  2. The framework automatically generates a set of HTML files with the defined routing.
  3. Take the output artifacts and push them to a server.

Here's where s&box comes back into the picture. The twist is that SSG can be done with Blazor — meaning the same Razor markup used in the game. And this time without the style restrictions. Though JavaScript is unavoidable the moment you need any interactivity.

If you've worked with Blazor before, you know it doesn't support SSG out of the box — only Blazor Server, Blazor WASM, and the PHP-ish Blazor Static SSR. That's where the community steps in. Search for a Blazor-based SSG framework and you'll find Blazor Static. Blazor already makes web development accessible to anyone who knows C#, and Blazor Static extends that to statically generated sites with the same ease — a huge shoutout to the devs for making it possible.

Building the site

So, Blazor Static. At its core it's a static site generator with a pretty specific purpose: powering blogs. And it goes beyond just the engine — there's a fully fleshed-out project template called BlazorStatic Minimal Blog that comes with everything ready to go:

  • blog posts from Markdown files;
  • tags and tag-based filtering;
  • Tailwind included;
  • responsive layout;
  • basic SEO via automatic sitemap.xml generation.

That covered most of what I needed. If you look around the site, you'll spot a couple of things that aren't in the template: language switching and a different blog layout. Fortunately the engine is flexible enough to add those without too much pain. And also there's the games page, which obviously had to be built from scratch.

In Blazor Static also supposed to be an interactive debug mode with Hot Reload, but the behavior differed enough from the static build that I ended up writing my own little startup scripts — triggering file generation and launching dotnet serve. Apparently Java left its mark on me.

Games

There's no escaping it: if you want something nicer than a Markdown-based page, you have to write the layout yourself. Having designed UIs in s&box, putting together a small .razor page shouldn't be a big deal — except we also need localization. Given the, let's say, modest game library, I could've just duplicated the page for each language, but I respect DRY too much for that. So I rolled a small custom solution: generating the page from a TOML file.

TOML has crept into my life in a surprisingly persistent way, showing up even at work — and ironically, s&box is to blame. For Terry Dev Tycoon I needed a lot of text in various formats: tracker tasks and in-game events. Here's a quick rundown of how I picked the storage format for the game:

  • Storing content directly in code isn't on the table.
  • The usual C# resource files are a no-go in s&box.
  • JSON seems like the easy choice, but personally I find the syntax unpleasant to read. It also lacks multiline string support, which was a dealbreaker for me — events needed proper paragraphs.
  • YAML is the next obvious option, but not ideal for game assets. The syntax is friendlier, multiline text works, but the tree structure was more than I needed, and YAML's parsing complexity is notoriously painful.
  • TOML turned out to be the right fit. Clean, readable syntax, no unnecessary nesting, multiline literals, and with Tommy you can add TOML support by dropping in a single file.

The same logic applied to the website, with the bonus that I could just pull Tomlyn from NuGet and call it a day. The TOML file holds everything: title, description in Markdown, links, screenshot paths. Duplicate for the other language, wire up a file-reading service that's language-aware, design the page, and enjoy the result.

I mentioned earlier that you'll end up reaching for JavaScript in Blazor — this is one of those moments. The screenshots cycle automatically, which requires DOM interaction. In Blazor Server or WASM you could handle that in C#, but on a static site it's traditional methods only.

Blog page

This part required actually digging into how Blazor Static works. The problem: I wanted to change how the blog listing looked. By default, it looks like this: And the current result, as of writing this post, looks like this: Setting layout aside, there are two meaningful differences:

  • posts have a cover image;
  • the excerpt comes directly from the article, rather than being set separately.

Getting there wasn't entirely straightforward since the process isn't documented anywhere explicitly. So I'll step away from the narrative format briefly and walk through it.

IFrontMatter

This is the bottom of the class hierarchy we care about. It's an interface for storing data from the Frontmatter — a YAML metadata block at the top of each Markdown file. For this post, it currently looks like this:

title: How to make a blog if you're a s&box dev
published: 2026-03-14
tags: [misc]
authors: [Right]

This is the data that shows up on the blog page and in the post's metadata (author, tags, etc.). The default implementation is BlogFrontMatter, but if you want custom metadata, you'll need your own class. Just don't forget to also implement IFrontMatterWithTags if you want tag support. And as you can see in the snippet above, there's no image or excerpt field — intentionally, since I wanted to pull those from the article content itself. Which means going a level deeper.

Post

Next up is a class from the Blazor Static engine itself — you can't modify it directly, but you can't skip it either. It's what carries the Frontmatter metadata, and the Post itself contains the generated HTML of the post.

We can't touch the engine internals, but nothing stops us from wrapping it. So for each Post, I create a PostWrapper. Inside that wrapper, using some simple but effective HTML parsing via AngleSharp, I extract the first image and the paragraph before it, turning them into the cover image and excerpt respectively. Then I just use PostWrapper on the blog page, instead of Post.

This setup was more than enough for my needs, and it ended up handling localization cleanly too.

Localization

When I first thought about this task, it seemed straightforward in concept but tedious in practice: make language a route parameter across all pages, set up a custom content loading pipeline for the blog, move all UI strings into resource files, pull TOML files by language. A lot of moving parts.

So I was pretty surprised when Cursor's Composer 1.6 handled it confidently. With constant supervision and reminders that this is a static site — so things like NavigationManager redirects simply won't work — but it got there. What impressed me most was that the agent figured out the post loading pipeline on its own and extended my wrapper accordingly.

I realize this section might feel underwhelming: a complex, interesting problem, solved via AI. Maybe that's just my perception. Either way, it's worth thinking about what "hard" and "easy" actually mean in our work nowadays.

Design

Deciding what you want your site to look like is genuinely difficult, especially when frontend design isn't your area. When I have to do it myself, I tend toward a clean minimalism — I think that comes through in Terry Dev Tycoon and Sboku Arena.

I'm also drawn to modern interfaces, and to the aesthetic that AI tools tend to generate. As it happens, that's exactly what inspired this site's look — specifically, the placeholder page that sat here during development: Fortunately, I didn't have to start from scratch. The Minimal Blog template already has a sensible layout, so no major restructuring was needed. It mostly came down to swapping styles across the pages and adding a small amount of custom CSS. Tailwind makes that feel minimal in practice, though it does take some time to get comfortable with. On the upside, Cursor is great at it. On the downside, AI is apparently putting Tailwind developers out of work. Draw your own conclusions.

Hosting

I mentioned at the start that ditching the backend saves more than just stress — it saves money too. Free static hosting genuinely exists, though the specifics vary by provider. Here's what I tried:

  • Cloudflare Workers: not the most intuitive interface, confusing documentation, but excellent DNS management in the same dashboard. I still use Cloudflare for that.
  • Netlify, Vercel: great interfaces, deploying straight from GitHub is a pleasure, and Netlify also makes manual deploys easy. Either of these is a natural first choice.
  • GitHub Pages: widely used, battle-tested, great GitHub Workflows integration as expected. That said, I only got it working on the second attempt and the docs weren't much help. Downsides: no redirect support, and free tier requires public repositories.

I tried them in that order. Why did I land on GitHub Pages?

For me it was a bit of a discovery, but everyone except GitHub Pages gets blocked in certain countries — Cloudflare especially, which is simply inaccessible in many places. So if maximizing your potential audience matters, the most accessible option wins. If GitHub Pages ever runs into the same problem, it'll come down to a choice: paid hosting or a smaller audience.

Afterthought

A few numbers: judging by commit dates, the site took exactly one week of evening work to build, plus another week trying out hosting options. Reasonably quick, all things considered.

Development probably isn't over either — I only just noticed there's no pagination, an RSS feed would be a nice addition, and I might rework the routing at some point. But for now, I'm happy with where it landed.

Anyway, that's the story of how this site got built. It went live exactly a month ago, as it happens. Next up will probably be the Terry Dev Tycoon post-mortem I've been promising. Follow along in Discord for announcements.