Stand by for reincarnation.
Welcome to the Version Five of my website. Quite a bit has changed, so let’s go over it.
New Theme
As you can see (at least, if you’re reading this online), the theme for my website has been redesigned from the ground up. Unlike the previous version, which was based on someone else’s theme, this design is entirely my own. The design is, in no small part, inspired by Brutalist Web Design[1]. It doesn’t look super shiny because it shouldn’t; it is designed, first and foremost, to be pleasant to read. There is a grand total of one (1) fancy animation used, and the overall style is pretty minimal, placing emphasis on the content above all else.
The main decorative choice I made (which is really halfway between brutalist and decorative) is the Markdown decorations. If you’re reading this post on the web, you’ll see a couple of things in the formatting of the body text are a bit unusual. The text of links is surrounded by square brackets and link destination is shown in parentheses, bold and italic text is wrapped in double asterisks and underlines respectively, headings have pound signs in front of them, etc. The goal of these styles is to replicate (to a certain extent) what the original, Markdown-formatted source of the blog posts looks like. With the exception of the heading pound signs, they’re entirely implemented in CSS using pseudo-elements, which has the added benefit that they’re not included in text highlighted/copied on the webpage. The pound signs next to subheadings in posts are the one exception to this and are actual tags because they serve a functional purpose: they provide jump links to the different subsections of posts. These are decorations and serve no functional purpose, but I still believe they’re brutalist because they reflect the raw content of the Markdown before it’s been processed. I think the decorations look nice and add some visual distinction to the design without being ostentatious or detracting from the experience of reading posts.
As for reading posts, I spent probably more time than I should have reading Practical Typography and fiddling with the fonts, trying to make the typography as pleasant as possible for reading articles. While I appreciate the author’s point about not using system fonts, for my own personal blog, I care more about speed and page weight than how unique the typography is. After much fiddling, I settled on Charter for the main body text and Avenir for the headings as well as the text in the header, footer, and comments sections. I chose them because they had the highest grade of any of the system fonts, and I think they look pretty good (both separately and together). Using a serif font for the body text, I find to be a much easier on the eyes compared to the sans serif font used by the previous design of this blog. A sans serif font for what I call the “UI” elements of the page provides a nice contrast with the body text. Code (both blocks and inline) uses Apple’s SF Mono if it’s installed (because it’s my personal favorite) and falls back on the browser’s default monospace font, because I find most system monospace fonts either ugly or too light weight.
Regarding color schemes, there are still light and dark variations. But, as you’ll notice if you look at the theme selector at the bottom of the page, there’s now a new automatic theme (which is also the default). It uses the prefers-color-scheme
media query to support dynamically adapting to the user’s preferred color scheme across their operating system. For users on operating systems or browsers that don’t support the media query, light mode is used by default as before. Also, the new color scheme used for code blocks is the Atom One color scheme for Highlight.js.
New Features
There are a couple new features, in addition to the new automatic theme-switching. There are comments (discussed more below). In addition to the main RSS feed, there are now feeds specifically for the individual categories (e.g., the feed for the meta category). There’s also an estimated reading time counter shown in the post metadata (it’s quite simple, it calculates the time by taking the word count divided by an average reading speed of 225 WPM[2]).
The site remains almost entirely JavaScript free. There are two places where client-side JS is used: the theme switcher and the comments section. There are ways to implement the theme switcher without client-side scripts, but I don’t believe it’s worth the trade off. The automatic theme is the default, and it’s what those with JS entirely disabled will get.
(There are also footnotes, which, so far at least, I’m getting a lot of mileage out of.)
The Backend
The previous version of my website used Jekyll (and WordPress before that, and Jekyll again before that). In what may become a pattern, I’ve once more switched away from Jekyll. Version Five uses something completely custom. It has been a work-in-progress in one form or another for about a year now. It started out as a Node.js project that was going to be a general-purpose static site generator. Then, around the time I was learning Elixir (which I love, and will be the subject of another blog post), I attempted to rewrite it in that[3]. Then we finally arrive at the current iteration of the current iteration of my website. In spite of my distaste for the ecosystem[4], I returned to Node.js. This time, however, the project took a bit of a different direction than the previous two attempts at a rewrite. It has two main parts: the static site generator and the ActivityPub integration.
Static Site Generator
The static site generator is by far the most important piece. Without it, there would be no website. I once again went with an SSG for a couple reasons, starting and ending with performance. When it comes down to it, nothing is generated at request time. Everything exists as static files on disk that are generated when the service starts up. The basic architecture isn’t all that special: there are posts written in Markdown, gathered into various collections, rendered to HTML using various page layouts, and then gathered together in various indexes (the main index, category-specific ones, and RSS feeds).
The current iteration, however, was a bit different in its conception. Instead of attempting to be a general purpose static site generator that anyone could pick up and use, it is designed to be completely specific to my needs and the requirements for this blog. The different collections of posts (blog posts, the different tutorial series) are hardcoded in. The Markdown pipeline used to render posts to HTML is hardcoded, and contains some custom extensions for the Markdown decorations used. The list of which files are copied verbatim is hardcoded. You get the idea.
I’ve toyed with the idea of refactoring out all of the custom code I’m using to generate the blog (as well as the ActvityPub integration) into a separate library which other people could use for their own blogs. It’s not entirely off the table, but if it does happen, it won’t be soon. For now, I’m perfectly content with the functionality my pile of hardcoded, janky, custom code provides me. Besides, this project has been ongoing for more than a year, and I don’t need it to drag on any further.
ActivityPub
It may be mostly static (and could be used entirely statically), but there’s one big difference: it runs a web server that’s responsible for serving the static files and for handling the bit that actually needs to be dynamic: the ActivityPub integration. (There’s another blog post coming at some point about resources I found helpful in getting AP actually working with other platforms.)
If you haven’t heard of it before, ActivityPub is the protocol that underpins the ‘fediverse,’ a network of federated software projects. Most of the current projects are aimed at being replacements for traditional social networks: Mastodon and Pleroma are microblogging implementations similar to Twitter, Pixelfed is a photo sharing platform similar to Instagram, and PeerTube is a video hosting service like YouTube. There are also blog engines that federate using ActivityPub, including Plume and WriteFreely, which were the inspiration for this project.
Could I have used Plume or WriteFreely instead of rolling my own custom solution? Yeah, probably. But I have no doubt I would have run into substantial problems along the way. (Off the top of my head, probably ones related to having separate collections for tutorial series as well as the Markdown decorations.)
The current ActivityPub is pretty bare bones. It implements the minimum necessary to allow: 1) looking up posts, 2) following the blog from other AP-implementing services, and 3) commenting on posts. You may be able to do other things with my posts from other AP services (e.g., favoriting and reblogging), but the code on my end doesn’t know or care. In fact, only handles a whopping four types of incoming activities: Follow, Create (only for Note objects), Delete (also only for Notes), and Undo (only for Follows). This may be expanded in the future, but for now I’m content with the functionality it provides.
The way I’ve set up comments to work here is pretty simple. The underlying ActivityPub objects are stored in the database, and whenever someone loads the page and expands the comment section, some client-side JavaScript is triggered. It sends a request to the back end to retrieve all the comments for the given post from the database, then turns them into a tree, renders them to HTML, and injects them into the page. This does mean that viewing comments requires JavaScript, but the other option would have been to have request pages be generated upon each request, making them no longer static pages. Given the trade offs, I opted to keep the article pages static and retain the performance benefits that brings. I think the compromise is worth it; most people have JavaScript enabled and those who don’t probably aren’t looking at the comments.
If you want to comment on any of my blog posts, just copy and paste the URL into the search field of your client and hit the reply button!
Technical Details
If you’re interested, here are some of the technical details about how the back end is implemented.
All of the backend stuff is written in Node.js and TypeScript. The SSG piece uses Markdown (with some extensions) for rendering posts, Sass (specifically SCSS) for the styles, and EJS for the templates. The ActivityPub integration uses Postgres with TypeORM to store remote actors and comments. And the web server itself is Express.js.
When the program starts, the static site generation is performed before the web server is started to ensure that broken or outdated files aren’t served. First, some files are copied over verbatim (such as favicons and the client-side JS for loading comments), the CSS files for the individual themes are compiled from the Sass files, and the error pages are generated. Then, tutorials and blog posts are generated by scanning through the respective directories and rendering any Markdown files. Finally, the home page, category index pages, and RSS feeds are generated.
That’s it for the generation, but before the web server starts, the ActivityPub module takes all of the posts, checks if there are any new ones, and, if so, adds them to the database and federates them out to all the remote actors following the blog. Then, all the web server routes get set up and the server finally starts.
By the way, the source code for the generator, ActivityPub integration, and content is self is all visible on my Gitea. (Note: the source for the generator and AP integration (the lib
directory) is open source whereas the contents of my website (the site
directory) is only visible source.)
Conclusion
A lot of stuff has technical, under the hood stuff changes, and I’d like to think that I haven’t wasted my time and that I’ll actually use this new version of my blog to publish posts. But, I don’t know what will happen and I can’t make any promises. I have some drafts of posts that I’d like to finish and finally publish, so stay tuned (you can subscribe on RSS or by following @blog@shadowfacts.net
on your favorite ActivityPub platform). As for the Minecraft modding tutorial series, those have been discontinued. They remain available for posterity, but they haven’t been updated (merely transplanted into the new blog), and I don’t currently have any plans to write new ones.
Also excellent is the Better Motherfucking Website. ↩
My sources for that number are pretty bad. It’s based on the reading time things from various other blogging engines. If you have better sources, let me know. ↩
Unfortunately, this attempt ran into some issues fairly quickly. Elixir itself is wonderful, but the package ecosystem for web-related things such as Sass, Markdown rendering, and syntax highlighting, is lackluster. ↩
The package.json
for the project explicitly lists 30 dependencies, 13 of which are TypeScript type definitions. There are 311 packages in my node_modules
folder. Enough said. ↩
Comments
Comments powered by ActivityPub. To respond to this post, enter your username and instance below, or copy its URL into the search interface for client for Mastodon, Pleroma, or other compatible software. Learn more.