Ken Muse

How I Blog (or How I Learned to Automate Everything)

The last couple of months have been a bit challenging to keep up with posting blogs. Unfortunately, there was a loss in the family, so it has taken a lot of time and energy. That said, I don’t regret the time I’ve taken for myself and my family. Life is short, and we need to make the most of it. The technology field doesn’t always provide us with a lot of personal time. It’s important to take it when we need it (and if you’re running a company, to encourage employees to create healthy boundaries).

That brings me to a topic that my colleague Josh Johanning has been encouraging me to post about for a while now. He pointed out that it might be interesting to see how I manage the blog and the process I use to create and publish content. It’s a bit of a behind-the-scenes look at how I manage the site. I hope you find it interesting.

The platform - Hugo

I’ve been using Hugo for a while now. I first learned about Hugo and its many features from Jeremy Likness, a former Wintellectual, frequent blogger, and Principal Program Manager for .NET Web Frameworks at Microsoft. I admired his posts for many years. Then I admired how effortlessly he seemed to be able to create and publish content.

Hugo is a static site generator that allows me to write my posts in Markdown. It then uses those to generate a static web site. It’s fast, easy to use, secure, and offers lots of flexibility. It doesn’t have any special runtime requirements, and it allows me to separate the content from how I’m going to display it. At the moment, I’m using a theme that was hastily adapted from a Bootstrap 4 based theme that I used when I ran my site on the Piranha headless CMS. At some point, I’ll probably modernize my theme, but for now it works.

I have a few archetypes I’ve created that I can use to quickly get started. If you’re not familiar with the concept, think of it as templates that Hugo can use to make it easier to quickly create templated content. I can create a new event or post without having to remember all of the metadata that I need to include. In fact, it writes a lot of content for me.

I use Hugo to preview the site while I’m writing the content, then to generate the files that are eventually published. Thanks to some recent changes that allow you to customize the generated Markdown for some tags, I’ve been able to slowly improve the accessibility of the site and the quality of how it generates the content. While there’s a few things I wish Hugo would add, I’m generally happy with what it provides. It’s been a great choice.

The editor

For years I used Markdown Monster. It’s the most feature-rich Markdown editor I’ve found. It was also the only one that supported posting my content directly to multiple sites. Unfortunately, it’s Windows only. Since I’m on a Mac at the moment, I’ve adopted Visual Studio Code with several extensions to make the editing easier. I build everything inside of a Dev Container, so the environment is portable between machines. I can also open everything in Codespaces to work on the site. The container installs my favorite extensions, plus configures a few tools that make my life easier.

To improve the experience, I also use a few Dev Container Features to install Dart Sass, Docker-Outside-Docker, and Jupyter (so I can use notebooks in VS Code for the occasional graph). Features are also a great way to install and configure some supporting languages. I have a handful of older scripts that help me validate the Markdown content, so I install PowerShell 7 to be able to run those. Jupyter requires Python and uses that for generating the charts, so I also install that. The JavaScript and SaSS templates for the theme are built using Webpack, so I install Node.js (with the packages managed using Yarn). It helps me to reduce the size of the pages and ensure that the site is fast.

I was originally using Alpine for the base image in the dev container. I’ve since switched to, which is built on Ubuntu 22.04. I had expected Alpine to be smaller and faster, but it was a constant fight to keep Hugo up-to-date and working. It was also very challenging to support any of the language tools. The prerequisites ultimately made it substantially larger than just using the Ubuntu base image. Once I realized that, it made no sense to keep fighting with it. I’ve been much happier with the Ubuntu base image. The dev container runs in Docker Desktop on my Mac (or PC) and Codespaces in the cloud.

Visual Studio Code Tasks make it easy to automate common tasks in in the IDE. I started using it to lint my files and do some basic link checking. Over time, I have defined tasks for starting Hugo (so I can see the site as I create it), creating the blog/event from the archetype command, and even updating the current file to publish it. If I do it more than once, I’ve probably automated it and made it into a VS Code task.

The extensions

At the moment, I’m using the following extensions in VS Code:

GitHub.copilot-chatI create a lot of code snippets and summaries. This tool saves me time doing both!
GitHub.vscode-pull-request-githubWhen I publish an article, it starts as a pull request. This lets me run a number of checks before anything is merged or goes live
github.vscode-github-actionsSupports editing the Actions I use for publishing
davidanson.vscode-markdownlintWonderful tool that helps me keep my Markdown clean
ms-azuretools.vscode-dockerFor editing the Dockerfile I’m using for the dev container
heaths.vscode-guidWhat can I say? Sometimes I need a GUID/UUID
streetsidesoftware.code-spell-checkerBest spell checker I’ve found for VS Code and my workflow
bierner.markdown-emojiFast and easy way to find and add Emojis when I need them 🔥 😸 💥
TakumiI.markdowntableMakes it easier to create and edit these tables
tamasfe.even-better-tomlIt’s nice to have language support for editing the Hugo TOML configuration files
mhutchie.git-graphHelpful tool for reviewing the Git commits
eamodio.gitlensAll the Git features that you wish VS Code had built-in 😄
tomoyukim.vscode-mermaid-editorhelps me when editing Mermaid graphs and lets me quickly persist them as images
redhat.vscode-yamlFor when I need to edit a YAML file
jock.svgEditing and previewing SVG files
hediet.vscode-drawioUltimate graphical tool for VS Code, letting me using ( to create SVG illustrations. I use this with two specific settings that are needed to make it render correctly: "hediet.vscode-drawio.theme": "Kennedy", "hediet.vscode-drawio.offline": true
nopeslide.vscode-drawio-plugin-mermaidAdds Mermaid support to the plugin
Janne252.fontawesome-autocompleteI use Font Awesome icons, and his helps me configure them
bierner.github-markdown-previewImproves the native Markdown preview support
bierner.markdown-checkboxAdds support for checkboxes in the Markdown
bierner.markdown-footnotesMarkdown footnote viewing support
bierner.markdown-preview-github-stylesStyles the preview to render similar to the GitHub site
bierner.markdown-mermaidMermaid support in the Markdown preview

All of these are added to the Dev Container, so they are preinstalled when the environment starts. I’m frequently adjusting this list, eliminating the ones I’ve stopped using and occasionally adding a new one for a missing feature.

Hosting - GitHub Pages

Originally, I used Azure Static Websites with Azure FrontDoor. That was extra cost and complexity for something that ultimately could be more easily handled for free using a feature built into GitHub. The performance of the site is incredible, easily surpassing what I had previously. It’s even faster now that I’ve added a CloudFlare CDN to cache and distribute the content. Now that Pages has better support for Actions, it has streamlined the publication process. I no longer need to maintain a separate branch with the generated HTML content.

GitHub Actions build and deploy

After I create a pull request, I use Actions to run some checks. This includes checking that the links within the site are pointing to valid content. At the moment, I’m not checking all of the outbound links. After several years, there are a lot of links to check and validate. Eventually, I’ll also add that in so I can clean up invalid page URLs.

Because Pages now uses Actions (and a build artifact), it’s easier than ever to publish the site. After I merge the code, the checks are re-run, the site is packaged and uploaded, and Pages deploys it. Everything is updated within a few minutes. Every evening, a new build is created and published. This allows me to schedule posts – sometimes weeks in advance. I’m constantly creating new content. When I’m satisfied that everything is ready to go, I have a task that updates the draft status and ensures the publish date is correctly set.

It’s a great way to manage the site.

Pull request running checks for publishing an update

The Markdown

Ever wondered what happens when I get behind? It usually means I haven’t had time to finalize the post and review it. Every post has some metadata I use for managing parts of the site. This is stored as YAML front matter at the top of the article. The current metadata I use:

titleThe post’s title
abstractThe short snippet you see on the home page before you come to the article
descriptionThe meta-description that’s used when you share the post. Defaults to the abstract.
categoriesAn array of categories to associate with the post
tagsAn array of tags for content in the post
postIdAutomatically generated GUID for uniquely referencing the post
draftIf this is true, it’s a work in progress and not ready to publish
publishDateIf the article is not a draft, this specifies the earliest date/time it should show up on the site. Builds after this date/time will include the article.
dateThe publish date as yyyy-MM-dd for organizing articles by date
monthThe publish month as yyyy/MM, for organizing articles by month
yearThe publish year, for grouping articles by year
seriesIf present, the name of the series (for grouping related articles)
slugThe URL snippet for the permalink (for this post, it’s how-i-blog)
bannerThe image banner that appears at the top of each page
banner-sourceThe URL for licensed images and notes for personally created images

When I create a post, the task expects the title in a specific form. For example, this post was 2024/2024-06-28 How I Blog. Most of the fields were then extracted from this format and automatically populated. The extra 2024 at the beginning lets me indicate I want to group this article with others from the same year in the folder 2024. Each post gets its own folder, making it easy to store related content.

When a post is completed, I’ll add the abstract and make sure it has an image. The image is automatically cropped and resized for the header. I generate multiple sizes to support different screen widths, minimizing the download size for mobile devices and supporting sharing on social media. The metadata is also used to create the Open Graph and Twitter Card tags for sharing on social media. This is all done as part of the site template.


Originally, my speaking events were stored as YAML data. I would then render out the list view. I’ve since moved to using the same approach as the blog posts. This means I have a page for each event, allowing me to render each event’s details in a consistent way. It also means I can use structured data markup for events on each page. Google uses that data to enhance the search results for my speaking engagements, showing me as an available event on the search results page. It took me a year to get everything mostly right. The key thing to know was that Google expects to find a single unique page for each event. It won’t show pages that have metadata for multiple events (like the list view).

The pages are optimized for Google Search, which drives several thousand visitors to the site each month (and creating around 3 million search impressions each year). By making the articles as practical and helpful as possible (or so I hope!), it makes it easier for people to find useful information. Every time someone visits the site from a search, it improves the site’s ranking and and makes the content more likely to be seen by someone else. I’m going to do some work to improve a few things for Bing (and Copilot) in the near future.

The number one factor for search engines? Constantly adding new content that is relevant and useful. The more content I provide, the more frequently they re-evaluate my site. How do they know how often to check back? It seems to be based on how often they historically have seen new content being created. Now that I try to provide a post each week, they check back much more often compared to when it was once a month.

At the moment, the site receives over 25K unique visitors each month, serving almost 366K requests and 4GB of data to visitors from all over the world. It’s a lot of fun to see the site grow and to know that the content is helping people. I was lucky to get so much free information when I was starting out. It’s nice to be able to give back. It’s also amazing to see how many people from other parts of the world visit the site. It’s a great reminder that technology is a global community.

Running a blog

At a high level, that’s how I manage my blog and schedule my posts. I hope you found that informative and that it provided some useful information you can use for your own site.

And thanks for visiting!