MkDocs — an odd but sensible choice
Faced with the need to scale my website beyond a few fancy landing pages, I make the seemingly odd choice of remaking it with MkDocs.
For the last few weeks, I’ve been overhauling my website(s). My previous site, powered by Ghost, is now relegated to a subdomain and is getting good service as a (mostly) membership-only site to deliver newsletters, serialised stories and book downloads, and my blog. Meanwhile, I’ve rebuilt my top-level site, ChrisRosser.net, using only static web technologies.
I had several reasons for making this change — mostly due to cost, time and reliability. Thankfully, I found a cheaper host, which means I can continue using Ghost, and don’t have to sell my soul (or my members’ personal data) to a third-party service like Substack.
I liked this approach a lot, but it doesn’t scale once a website grows beyond a half-dozen pages. I have a landing page and pages for each of my published novellas. However, I want to add more books and stories, my world codex (two of them) and maps, and maybe a blog for news updates.
The solution to this problem is a static-site generator (SSG). You can think of these as content management systems (like WordPress or Ghost) but one that doesn’t require a server or database to run. Instead, they run locally (or through a remote build system) and turn Markdown files into HTML using templates.
There are heaps of SSGs available, with Hugo and Jekyll being among the most popular for content-heavy sites like blogs. Before I migrated to Ghost, I used Pelican. However, I’ve taken the odd path in using MkDocs. I say odd because MkDocs is designed for technical writers producing technical documentation. But, I have three good reasons…
Firstly, I am a technical writer, that’s how I’ve made my living for the last 20 years. I’m intimately familiar with MkDocs and know exactly how to bend it to my will. MkDocs is based on Python and the Jinja2 templating language, which incidentally powers Pelican. I’ve been a Python developer for almost as long as a technical writer, and I’ve used Jinja2 since 2014.
Secondly, writing a world bible or codex for a fantasy or sci-fi world is a documentation project. MkDocs is built for creating docs, not blogs, although you can add blogging support using a plugin. While I’m sure Hugo and Jekyll can be wrangled into the job, I don’t have the time or inclination to do so.
Thirdly, composing all my site's content in MkDocs means I don’t have to set up a new subdomain. If MkDocs can make a pleasing landing page (spoiler alert, it can), then I can store all my site’s content — landing page, books, codex and more — in a single repository and deploy it to a single domain.
Creating the MkDocs theme
So, on a whim, I spent a couple of hours converting my stackless website into a MkDocs theme. Fortunately, this was a trivial task. Jinja2 is HTML sprinkled with an easy-to-learn template syntax that lets you add conditional code, looping and placeholders for data. The concept is no different to creating WordPress or Ghost themes using PHP and Handlebars, respectively.
So far, I’ve recreated my home page and books, now individual Markdown documents rendered against a specified custom template. I can now store data in YAML directly into each markdown file’s header, making it much easier to edit if needed. This data is then used in the template’s HTML components.
Thanks to Jinja2, I can also reuse elements easily across the site by breaking them into individual components and calling them when needed. For example, the series section I display on my home and books page uses the same component, even though they are different templates. Best of all, I can add a new series by updating the book’s page YAML header instead of editing and duplicating the code.
The content shown in the series widget is generated from simple YAML embedded in the page’s header. As shown in the excerpt below, it’s much easier to edit than doing so in HTML.
--- title: Books by Chris Rosser template: 'books.html' series: - title: "The Weaver Cycle" subtitle: "Grimdark Fantasy" bg: "skeinhold.webp" books: - title: "Cadoc's Contract" cover: "cover-cadocs.webp" url: "/books/cadocs-contract/" - title: "Mistress of Skeinhold" cover: "cover-mos.webp" url: "/books/mistress-of-skeinhold/" - title: "The Codex of Destiny" subtitle: "" bg: "codex-banner-web.webp" books: - title: "The Codex of Destiny" cover: "cover-codex-of-destiny-bw.webp" url: "/books/codex-of-destiny/" ---
Thanks to MkDoc’s flexibility, I can add anything I want and reuse it in the generated site, any way I choose. This beats the pants off working with rigid database tables and means I can adapt the site to include other forms of structured content as my whims change.
Similarly, I’ve offloaded most of my site’s design elements to MkDocs theme settings. Colours, fonts, background images, et cetera., are all configurable in the site’s mkdocs.yml file.
Codex, blog and more
My next task is to flesh out the main template, which I’ll use for most codex articles. I’ve attempted something like this before— a static, more open alternative to WorldAnvil. That wasn’t much more than a proof-of-concept, and I’ve learned much since then.
I might — emphasis on might — add a blog to the site, too, though I’m unsure if I should, given that Ghost currently performs this task. Doing so might allow me to further boil down Scriptorium to delivering newsletters and hosting content like my serialised stories and DRM-free ebooks behind my membership wall. If I reach that point, however, I might decide I can do all that with cloud functions tied to services like Mailgun, Stripe and Amazon’s S3 and no longer need to run a server. This a problem for future Chris to consider.
Another problem I have to solve is hosting interactive maps without a server. I’ve done something like this before using LeafletJS and JSON on the client side but used a local web app to add elements before creating a static snapshot for deployment. Obsidian has a plugin for this purpose, but that app has the benefit of a NodeJS runtime it can leverage. I could take a similar approach to authoring the map. We shall see.
Sharing with others
I’m probably the only person who’s wrangled MkDocs into making a fantasy author’s website, complete with a world-bible codex. Even so, my concerns aren’t unique. Authors need websites, and readers want content. Authors also don’t make much money, so building a platform on the cheap instead of being gouged by third-party service providers is a bonus.
Much of the way I’m architecting the theme is to decouple my creative content from the theme’s code. Nothing’s hard-coded in the theme, so in theory, another author could make and customise their site with minimal effort.
As noted, most of the visual aspects of the site — fonts, colours, and background images — are all configurable or contained in isolated CSS files. The current sub-theme is called
grimdark, based on what I mostly write, I intend to add themes for high/epic fantasy, sci-fi, and even contemporary genres like romance and thrillers.
Open sourcing something has its set of problems — like people demanding support, for example — but I would be prepared to do so given interest.
I think I’m settled. MkDocs, while not for everyone, works for me. It’s a wonderful bit of tech that’s malleable enough that I can shape it into anything I want. Using a static-site generator provides me with the ability to scale my site sustainably, using a content creation workflow I’m very comfortable with, and has the added bonus of being able to deploy completely free of charge.
Sure, I’ve got some issues to solve, but that just makes life interesting!