Reflections on Nitride

This week's goal was to get migrated over to MfGames.Nitride. It was a lofty goal, mainly because I've been making small incremental changes over the last year but never got something pushed over the finish line.

I succeeded.


This is my third “big” site for Nitride, one that follows the common pattern of pulling in multiple Git repositories from my forge, integrating the data, and then creating a website out of it. In other words, one of the main reasons I created Nitride.

It isn't exactly a fancy site, but I find that I'm not really creating flashy sites anymore. Part of this is because I still enjoy Gemini, which is a text-only format, but also because the chrome is distracting to me. I want something simple but not the black on gray of Netscape Navigator.

Overall, I'm pretty happy with the results. It creates landing pages for projects, pulls in the ./docs/ folder for each project and formats them (for example, MfGames.Gallium), and merges news posts from the projects (while decorating them) and creates a single unified feed.

There are more things I want to do, such as links to issue tracking, licenses, and other stuff. I also ended up losing some features like the culture sandbox (which I needed to rewrite anyways), the slippery maps, and a few other minor things.

Still happy with it.


I was working on debugging creating news posts in individual project repositories anews posts from individual projects into as my “little thing” for today and I came up with the exact period of time I decided to write my latest static site generator (SSG):

$ git log --pretty="%ai %s"
2021-05-04 02:27:28 -0500 feat: implementing my own
2021-04-07 22:13:20 -0500 chore: updating Statiq libraries
2014-05-07 14:41:27 -0700 Initial commit

It all started a year before, when I migrated from CobblestoneJS (my SSG from the early 2010s) to GatsbyJS. Which was pretty and gorgeous and so difficult to debug. It was also fragile for me because I don't update my libraries to follow the curve and every time I tried to add a new feature to the site, I spent a few days just trying to figure out the node packages to get everything building again first.

When I got into Gemini for the first time, I also realized that GatsbyJS wasn't going to work there because that shiny site was not text friendly. It was also tied into my frustrating that going to a page the first time downloaded the content in one manner, but navigation was done via SPA and pulled it from another; and the cache was killing me there.

(But it looked so pretty.)

So I decided to look for a new SSG. I didn't want to write my own because… I really don't like writing utility programs. But I wanted to do something Gatsby didn't do, which is generate a site that didn't require Javascript.

I figured I was good at C# (this was before Rust, but I'm still best at C#), so I wanted to find a C# static site generator in hopes that the NuGet package management wasn't as excruciating as Node's.

So I found Statiq which seemed like a perfect match. But going from GatsbyJS to Statiq quickly hit a point where I realized I was, once again, forcing a program to do things it wasn't supposed to be doing. The patterns in Statiq were nice but they were too rigid for the complexity of my way of creating sites:

  • The fragility of pulling in 80+ Git repositories into a CI pipeline
  • The use of multiple dynamic lists on a single page
  • The desire to make a Gemini-friendly site

After that month of thinking, I started to write my own. This came when I was looking at Statiq's document type realizing that my desire to create a new type basically meant I had to rebuild the entire framework to support a slightly different enumeration.


Enter Entity Component Systems (ECS) which I learned about while pondering writing a game using Bevy. I had never heard of an ECS before that, but the idea was great. Why not write a static site generator that didn't need a single enum to drive processing, where I could add arbitrary components to pages such as nested lists or metadata, and then easily be able to use them.

Of course, then I had to look at existing ECS systems in C#, but I eventually decided to be more idiomatic to C# (another frustration of Statiq) and focus on something that let me use LINQ-like method calls (such as .Select() and .Where()).

List<Entity> newsList = fileList
    .Run(this.setInstantFromPath, cancellationToken)
    .SelectEntity<UPath, Instant>(this.pathService.SetPathFromInstant)
    .Select(entity => entity.Set(IsPost.Instance))
    .Select(entity => entity.Set(IncludeInFeed.Instance))
    .Where(entity => entity.Has<IsMarkdown>())
    .OrderBy(entity => entity.Get<PageModel>().Title)

I'm specifically not saying Gallium is a “high performance” because it isn't. It is an ECS for relatively small systems that builds on existing libraries for patterns. I don't need high performance for sites with only a few thousand pages, I want something easy to understand.

Next Steps

There are quite a few steps going forward. I still want to make Nitride easier to use, Gallium to track LINQ patterns, and to present more data. But this is a big step and I'm really happy that I'm proud of (I'm sure there will be tweaks). More importantly, I have something that hasn't given me any major trouble updating since I started three years ago.