So while everyone is sort of in the process of rediscovering the way this World Wide Web works before we were all on the big platforms hating everything, I want to share how I make my own websites because I use a really cool tool called a static site generator that requires a bit more effort on my part, but gives me complete control over everything and lets me automate away some of the tedium of running a static site (I'll explain what that is in the next section).
It lets me focus on my content, only having to add the bare minimum of JavaScript and CSS that I need to make things interactive and look pretty. It's the closest I've felt to my high school days of slapping stuff on Geocities with nothing more than notepad and the copious amount of time an introverted high school nerd has. While there is some code involved, I don't think there's so much that it requires a professional-level dev like me to figure out, and a lot of work has already been done for you.
It's not the only way to make a website or even really the most popular way these days (I'm going to talk about that too), but I think it's a very accessible way that lets you have full control and seems to be generally unknown outside a circle of programmer weirdoes like me, so I am writing up this series of posts talking about it.
And just to reassure you that you can make a nice, modern site that can have a lot of content I suggest you take a look at my sites for my upcoming text adventure Quoll and The Fantabulous Season of '40, a TTRPG campaign I mean to run some day. I'll keep pulling examples from these as I explain. Any way, let's get started by talking about what a static (web)site and static site generator are, starting by comparing them to "dynamic" web applications (like Cohost!) that tend to be more common online. This is mostly going to be a bunch of introductory fluff that I will expand on with some practical details in future posts, so feel free to skip over stuff you're already familiar with.
So this post is meant to follow on by showing you some example code of all the pieces that make up how I actually do all the cool stuff I mention in the post I'm replying too. Don't get too deep in reading all the code blocks here, they are meant to be examples of what they look like and are about, not something you should commit to memory. (Feel free to copy for your own use, though!)
Note that I am going to be specific to my process of using Statiq with the particular design decisions that I made, mostly drawing from The Fantabulous Season of '40 website as that website is much simpler, and you can see the code I'm using in this Github Repository. However, I will also draw some examples from the website for Quoll as that one is more "filled out" and has very relevant Dev Diaries that illustrate how to make a blog.
I don't intend to teach you everything, just layout the land so you know where to begin looking! I will link to the documentation that I feel most relevant in a lot of cases where you can get a more full teaching. Let's get started by looking at the content!
The Content: Oh Hi, Markdown
HTML is Good. Of the three major web technologies (HTML, CSS, JavaScript), it is the least controversial. But it is pretty verbose, as you can see in this example from the generated index.html page for Fantabulous '40:
<details class="ooc-body">
<summary class="ooc-heading">
Psst! Click here to break kayfabe...
</summary>
The Fantabulous Season of '40 is a Thirsty Sword Lesbians campaign, and this is its website. It's written in
multiple levels of fictional truth (that is, "kayfabe"). <a href="kayfabe.html">Click here for more on kayfabe</a>,
or <a href="home.html">click here to go to the home page</a>.
</details>
You can definitely write this by hand (in fact, I essentially did write this by hand...), but there's a lot of distracting tags like <a href="home.html"> or <summary>. You may need to use weird escape sequences like "e; for quotation marks (don't have this in this example, though...). Is there a better way? Arguably.
That way is Markdown, which you're likely already familiar with as that is the format you use for posting on Cohost! Other websites like Github also let you use Markdown for entering in rich text. The basic idea is that Markdown ends up being much more friendly for people to write, while still being able to be converted pretty closely to HTML.
This makes it a great input format for a static site generator as I find it easier to write something like:
If you're wondering about that first command, "XYZZY", that is a debug-only command that puts the game in a special test
mode. This command, "XYZZY" kicks off an Inform 7 Action called appropriately enough, "invoking test mode". You
*can* enter that command into a release version of the game, but you'll only get a snarky message from Ada. As far as
to why I chose those letters, check out [the Wikipedia article](https://en.wikipedia.org/wiki/Xyzzy_(computing)) on it.
Which will produce the following HTML:
<p>If you're wondering about that first command, "XYZZY", that is a debug-only command that puts the game in a special test
mode. This command, "XYZZY" kicks off an Inform 7 Action called appropriately enough, "invoking test mode". You
<em>can</em> enter that command into a release version of the game, but you'll only get a snarky message from Ada. As far as
to why I chose those letters, check out <a href="https://en.wikipedia.org/wiki/Xyzzy_(computing)">the Wikipedia article</a> on it.</p>
I find the Markdown syntax to very closely mimic what I would have done if I was limited to plain text in the first place, so I can spend less time thinking about all the HTML tags, and more time thinking about the content. And if forced to, you can even embed HTML into Markdown, which was actually what I did for the first example above!
Markdown thus is what I write most of my content in and it is the default expected input file format into Statiq. But there's another feature of how Statiq reads in Markdown that is also very useful called "Frontmatter".
The Frontmatter: Getting Meta with Data in YAML
While Markdown is great for the actual content or the data of your site, another big part of static site generators like Statiq is being able to read in metadata. This metadata is usually put at the top of Markdown files in a format called YAML. Since this is at the top of the document, it's called "frontmatter" and here is an example from a Quoll dev diary:
---
title: 'Inform 7: Easy for Writers, Hard for Programmers'
date: 2022-03-21 10:05
creator:
- role: author
text: Jayce Mitchell
---
Note that this just defines a set of key-value pairs of information, and the idea is to define the data that describe the file. So I have a title key with a value representing the title of this dev diary entry. I also have a date key with a value representing the date when I wrote the entry. And last, a creator key that specifies the author of the file as another nested set of keys.
This frontmatter is very powerful! For example, if I wanted to specify tags for a post I could do something like this much fuller example of metadata frontmatter, which I took from a test I wrote for an ancient attempt to add some batteries to the predecessor of Statiq (so ancient that I had to replace my dead name):
---
Identifier: 8f0e9e4b-544d-4357-a6e9-f38d47812209
Title: Your Friendly Neighborhood Acme Inc. Salescoyote
Description: A lonely housewife gets a visit from her friendly neighborhood Acme Inc. salesman, who happens to be a coyote with some exciting products.
Creator:
- role: Author
text: Hank Raven
file-as: Jayce Mitchell
- role: Copyright Holder
text: Jayce Mitchell
Rights: ©2015 Jayce Mitchell, CC BY-NC
Language: en-US
Work Type: writing
Content Rating: T12
Content Descriptors: Alcohol Reference, Crude Humor, Drug Reference, Partial Nudity, Cartoon Violence, Suggestive Themes
Tags: transformation, cartoon, furry, coyote, rabbit, magic, fantasy, satire, male, female, mind shift, reality shift
Parent: 47983827-2601-46c3-aee9-4f9a198d5d19
Previous: bc243614-e943-4e7d-a3b2-574a928adb06
Next: 51cd27cc-79ab-403f-8866-2aabb19a17dd
Created date: 2015-09-13T01:21:13
Modified date: 2015-09-15T01:21:13
Published date: 2015-09-16T01:21:13
---
(I never wrote this story, sadly. It was just test data.)
You can also use YAML frontmatter to set various layout settings as I do in this example from Fantabulous '40:
---
title: The Fantabulous Season of '40
hide-title: true
hide-site-title: true
hide-header: true
---
These hide- keys are used to hide certain parts of the layout on those pages. Speaking of layout, let's continue our journey and talk about that next.
Layout Templates: Twirling Our Handlebars
While Markdown handles the main content, I write my Markdown to only include the main content. But if you look at the source for the Fantabulous `40 landing page, you'll see a whole lot more there than just my main content. This is the main "layout" of the website, and I use a set of Handlebars.js templates to write those. For example, the main layout of the site is defined in a file called "_Layout.hbs", which you'll have to look at on Github, because Cohost is a bit too smart to let me embed HTML and JavaScript code blocks it seems.
It is mainly all the boilerplate HTML code that does stuff like pull in the fonts I use, links the CSS style sheet, but note that all the sections between double curly-braces {{}}. That's the template part of the template, and when my bootstrapper program runs, they will replaced with the relevant information. For example, the main content gets added to {{{>textWhatPeople}}}. This uses triple curly-braces because it's technically a "partial". Rather than explaining that here, I will direct you to Handlebars.js's own documentation on it.
Note you can also use Handlebars templates to write conditionals (like the {{#if hasBodyClass}} conditional. But even more, you can have a section of HTML that is created for each entry in a list of entries, which is what I do in the template for the Quoll Dev Diary Index, which I sadly can't add...
Basically, in my bootstrapper, I pass in a previousDevDiary list and it will make a link using the information from that in the {{#each previousDevDiary}} Handlebars construct. This way, I don't have to add each dev diary by hand, the bootstrapper will just know what all files I have in my folder. Once again, I'm not going to go into details here, check out the Handlebars.js documentation for how this works.
That covers all the pieces of the HTML, but let's talk a quick bit about the other web technologies that make up the website.
Looking Good and Doing Things: CSS and JavaScript
If you look at the example layout template above you can see that I also link a few CSS and Javascript files.
There's really nothing special about these, I just have them in my input folder too, and they are copied to the right directory by the bootstrapper. Well, actually I use SASS to generate my CSS, but that's unimportant right now.
I tend to keep my JavaScript files very limited, for example, which you can see in my "layout.js" file that gets included above, which uses jQuery for even more conciseness. It has just enough code to handle showing and hiding the header. I believe strongly the site should be as functional as possible without JavaScript enabled, so I tend to keep it small!
(Sadly, can't embed it here on Cohost it seems!)
If you would like to know more about the web technologies CSS, JavaScript, as well as HTML, I recommend the Mozilla Dev Network docs which are the closest thing to "official" documentation for these.
So that's all the pieces, let's actually put them together using this much mentioned bootstrapper!
Putting It All Together: The Bootstrapper
The real magic of Statiq happens in the small "bootstrapper" program that I wrote. While you can look at the file in the repository, I'm going to copy the entirety of it here too, as well as repeat the most relevant pieces (with the indentation graciously removed):
using System.Linq;
using System.Threading.Tasks;
using Statiq.App;
using Statiq.Common;
using Statiq.Core;
using Statiq.Handlebars;
using Statiq.Web;
using Statiq.Web.Pipelines;
namespace Website
{
class Program
{
public static async Task<int> Main(string[] args) =>
await Bootstrapper.Factory
.CreateWeb(args)
.ModifyPipeline(nameof(Content), pipeline => pipeline
.WithPostProcessModules(
new SetMetadata("layout", Config.FromContext(async context =>
await context.FileSystem .GetInputFile("_Layout.hbs").ReadAllTextAsync())),
new RenderHandlebars("layout")
.WithModel(Config.FromDocument((input, context) => new {
pageTitle = input.GetString("title"),
baseUrl = ".",
hideTitle = input.GetBool("hide-title", false),
hideSiteTitle = input.GetBool("hide-site-title", false),
hideHeader = input.GetBool("hide-header", false),
bodyClass = input.GetString("body-class"),
hasBodyClass = input.ContainsKey("body-class"),
}))
.WithPartial("headerPartial", Config.FromContext(async context =>
await context.FileSystem .GetInputFile("_HeaderPartial.hbs").ReadAllTextAsync()))
.WithPartial("hideHeaderIcon", Config.FromContext(async context =>
await context.FileSystem .GetInputFile("_icons/CloseMenu.svg").ReadAllTextAsync()))
.WithPartial("showHeaderIcon", Config.FromContext(async context =>
await context.FileSystem .GetInputFile("_icons/OpenMenu.svg").ReadAllTextAsync()))
.WithPartial("textWhatPeopleRead",
Config.FromDocument(async input => await input.GetContentStringAsync())),
new SetContent(Config.FromDocument(document => document.GetString("layout")))))
.DeployToNetlify(
"bfbed18f-284f-47c4-a365-06b1915d0beb",
"TOKEN")
.RunAsync();
}
}
This is the "meat" of the process, this code mostly depends on the default Statiq "Web" bootstrapper (CreateWeb(args)) but with a few additional "post-processing" modules to load and render the layout:
new SetMetadata("layout", Config.FromContext(async context =>
await context.FileSystem .GetInputFile("_Layout.hbs").ReadAllTextAsync())),
new RenderHandlebars("layout")
.WithModel(Config.FromDocument((input, context) => new {
pageTitle = input.GetString("title"),
baseUrl = ".",
hideTitle = input.GetBool("hide-title", false),
hideSiteTitle = input.GetBool("hide-site-title", false),
hideHeader = input.GetBool("hide-header", false),
bodyClass = input.GetString("body-class"),
hasBodyClass = input.ContainsKey("body-class"),
}))
which uses some partials, including some icons for the button and the actual main content (textWhatPeopleRead):
.WithPartial("headerPartial", Config.FromContext(async context =>
await context.FileSystem .GetInputFile("_HeaderPartial.hbs").ReadAllTextAsync()))
.WithPartial("hideHeaderIcon", Config.FromContext(async context =>
await context.FileSystem .GetInputFile("_icons/CloseMenu.svg").ReadAllTextAsync()))
.WithPartial("showHeaderIcon", Config.FromContext(async context =>
await context.FileSystem .GetInputFile("_icons/OpenMenu.svg").ReadAllTextAsync()))
.WithPartial("textWhatPeopleRead",
Config.FromDocument(async input => await input.GetContentStringAsync()))
There's also some code to handle deploying to Netlify, which I had my deployment token in plain text until I realized that there's a lot more eyes on that token now. (It's been deleted and invalidated, don't worry).
That's really the only "custom" C# code that I added for Fantabulous '40! This code makes an executable that I run that does all the work of reading in all the input files, converting the Markdown to HTML using the Handlebars templates, and copying over the other files to make the site run. If you don't get all fiddly like me, it's possible you could just use the default "Web" bootstrapper!
As just an example of what a more complicated example looks like, let's look at how the Quoll Dev Diaries are created, which also just involves a few more extra "post-processing" modules, but I'm only going to show the one that handles writing out all the links to the previous dev diaries:
new RenderHandlebars()
.WithModel(Config.FromContext(context => new
{
previousDevDiary = context
.Outputs
.FromPipeline("Generate Dev Diary HTML")
.OrderByDescending(rn => rn.GetDateTime("date"))
.Skip(1)
.Select(rn => new
{
fileName = rn.Source.FileNameWithoutExtension,
title = $"Release Note {rn.GetDateTime("date"):yyyyMMddHHmm} - {rn.GetString("title")}",
})
}))
.WithPartial("currentDevDiaryPartial",
Config.FromDocument(document => document.GetString("currentDevDiaryPartial")))
Note that for Quoll, I use a feature of Statiq called "pipelines" that allow me to take the output of one pipeline and use it to feed another. So in this case, I take the output "Generate Dev Diary HTML" pipeline, which generates the individual Dev Diary pages, and use that to set the previousDevDiary list, skipping the most recent one, since that is instead rendered as an excerpt on the page. (Also note the bug I just noticed where I call them "Release Note"s because I copied this code from the code for setting up the release notes...)
I won't go into details here, but I hope that gives you an idea of the powerful customization that Statiq offers, I recommend looking at the Statiq documentation to learn about that, because this post is already way too long again.
The Wrap-up: I Have the Power
And that's it, those are examples of all the pieces that together, make up how I use Statiq to do websites. To summarize, I write my content in Markdown, and then use Handlebars templates to lay out that Markdown on the HTML page as well as cover common pieces of the website. I put YAML frontmatter containing the metadata for the pages, which can be used by the Bootstrapper program to decide what processing to do on each page. And then to round it all out, I have minimal CSS and JavaScript to make things look nice and do stuff.
I haven't used it yet, but Statiq has modules for generating RSS feeds and the like, too! Yeah, there's a bit of work setting up my bootstrapper and templates, but after that, it's just adding new content, re-running the bootstrapper and uploading the output to Netlify. And yet I have the power to customize any part of this process!
I thought I might have another part to this series, but honestly rather than me yammering on about it, I hope this outline of the pieces with the post describing the motivation helps you understand why I think static site generators are cool. Statiq is not the only one, comments on the other post has already mentioned one named Gatsby and I suggest you look around to find what works great for you!
For me, I like Statiq mainly because it's so customizable and it's in C#, which is my preferred general languages because I've spent too far in Microsoft-land. Further, I like that this generates just plain old HTML, CSS, and JavaScript and thus isn't a monstrosity of JavaScript and back-end services like the big web applications.
It's a more sophisticated tool than the Notepad, immense patience, and library books I used in high school, but it's not fundamentally different from that, with all the power in my hands, but a lot of good automation of the more tedious stuff.
Pretty cool, eh?
