Impetus
In September 2019, I had a problem with the integration of ActiveDirectory data in SQL and wanted to document the solution somewhere. I remembered my orphaned blogger.com thingy running under the domain kiko.io, but found it rather creepy and no longer in keeping with the spirit of the times. Something new was needed and I wanted full control over the code and the posts. I was particularly excited about the Static Site Generators based on Node.JS that were emerging at the time, because they stood out from the server-side construction method using ASP.NET or PHP that was prevalent at the time and generated pure HTML as before.
My old blog at zerbit.de, where I wrote about technology stuff between 2005 and 2013 in a very similar way to today, but in German and under ASP Classic or later ASP.NET on Windows Server 2003, no longer existed at that time. I also wanted to start something completely new and write in English, firstly to increase the possible audience and secondly to train myself in the only foreign language I was still familiar with from my school days.
Inevitably, I ask myself why I blog at all, keyword: “audience”. To save my future self from having to research again? Surely. To attract attention? Surely. To make a name for myself? Certainly, that too. Joel Chrono put it quite well in a recent post Blogging Expectations. I think every blogger wants to be read, otherwise they would write it on the wall in the basement. Not admitting that is self-deception. Am I being read? More likely no. Can I deal with it? Yes. Am I happy about any positive feedback? Definitely :D
Technology
My selection in 2019 for the new blog was short and fell on Hexo, an open source SSG that is more commonly used in Asia. It stood out for me because it seemed to be infinitely expandable with themes, plugins and plenty of API. This is true, but I quickly learned that complexity, a lack of documentation and a very small number of maintainers get in the way at some point.
I used Hexo’s default theme called Landscape because the layout includes a large hero photo and I intended to integrate my many photos into the design right from the start. In the meantime, I have done this so excessively that my site will probably never get an A+ performance ranking. In addition to the styles, a Hexo theme also contains some of the functionality of the system, which I have revised over the years to such an extent that I will never be able to use another theme, making the whole concept obsolete for me. Unfortunately, you can’t expand or omit themes in Hexo.
What was new to me at the time was the template engine EJS integrated into Hexo, which is used by so-called generators to render the desired HTML from simple Markdown files in which the posts are stored. In the Frontmatter (YAML format) in the header of each Markdown file, the parameters and additional information of a post are defined which the generator needs for its work.
Hexo offers the option of creating simple Pages and Posts with a time reference, as well as categorization and keywording, whereby only the latter are listed in the various overview pages. Using Tag Plugins based on Nunjucks, more complex HTML content can also be inserted into Markdown and I have made some use of this.
Plugins & Tools
Hexo can be easily extended to your own needs using plugins, and in my environment some are now indispensable, as are other JS tools that I use in self-written generators:
- hexo-asset-link - Convert Markdown style asset links to HTML
- hexo-generator-alias - Generate alias pages for redirecting to posts
- hexo-generator-anything - Generate index pages from custom front matter variables
- hexo-generator-copy - Copy static files from static_dir to public_dir
- hexo-hide-posts - Hide specific posts via Frontmatter
- feed2json - Convert RSS/Atom feeds to JSON
- front-matter - Extract meta data from documents
- handlebars - Build semantic templates
- puppeteer - Control headless Chrome
- exifr - Read EXIF from image
- sharp - Convert images
- imagemin - Minify images
- pagefind - Create static search index
Build
I rely on NPM and Grunt to build the blog. Grunt is responsible for downloading the fonts used and for bundling and minimizing the external asset scripts and styles.
To build and start my blog locally from scratch, the following commands are required in sequence:
- grunt
- hexo clean
- hexo generate
- npx pagefind
- hexo server
I have created various combinations of these as NPM run scripts, including npm run build (steps 1, 3 and 4) for deployment.
JavaScript Client-Libraries
For a little more functionality, I use a few external scripts that are integrated into the HTML pages that need them:
- Macy - Masonry layout
- Spotlight - Lightbox gallery
- Tiny Slider 2 - Image slider
- Medium-Zoom - Zooming images
- Image Compare Viewer - Compare before and after images
- downupPopup.js - Bottom Sheet dialog
- AutoTyping.js - Animated typing effects
- LC Select - Featured vanilla javascript dropdowns
- Leaflet - Mobile-friendly interactive maps
- Scroll-Timeline - Reading progress bar
- QR-Code-Styling - Generating QR codes with a logo and styling
- Nearest-Color - Find the nearest color
- TinyColor - Color manipulation and conversion
- ColorNames - Maps color names to HEX color values
- Vibrant.js - Extract prominent colors from an image
Hosting
I host the entire blog publicly on GitHub and thus commit every change, be it a code adjustment or a new post. Web hosting is handled by GitHub Pages via the address https://kristofzerbe.github.io/kiko.io, to which I have bound my domain kiko.io via DNS entries.
My domains are hosted at United Domains and 10 other domains point to the main domain kiko.io, which are parked there:
- kristofz.de
- kristofz.me
- kristofz.net
- kristofz.social
- kristofzer.be
- kristofzerbe.com
- kristofzerbe.de
- kristof-zerbe.de
- zerbit.de
- zerbit.net
Deployment
A commit to the GitHub repository automatically triggers the deployment of the entire code via a dedicated GitHub action called Build & Deploy kiko.io, which contains three jobs:
A. build
- Checkout
- NPM Install
- Run Build
- Commit Changes on MD Files (updated)
- Upload artifact
B. deploy
- Deploy artifact to GitHub Pages
C. finish
- Checkout
- NPM Install
- Run Webmention
In the last step, the code is checked out again to be able to execute a Hexo Console script called webmention, which sends webmentions for all URLs of the latest blog post. Unfortunately, this only works in an extra step because the referring page of the webmention has to be online at this point.
Local
I like to work with different device classes without interruption, be it my smartphone, tablet or various notebooks. That’s why the local repository is stored entirely in an OneDrive folder, which is synchronized to all devices. On Android, this is done by OneSync, although the Git files are not taken into account there, as there is no really useful Git client for Android.
As soon as I have finished a new post or another change, I commit to GitHub from the computer I am currently sitting at and the deployment is executed automatically as described above.
Writing
I write my posts mainly in my favorite editor, Visual Studio Code, where I also develop all infrastructure code related to my SSG Hexo. This is the only place where I can start the whole thing and see if it works. The only exception is Android on my smartphone, as there is no reasonable VS Code-like solution for this. Here, I switch to Obsidian, my central knowledge base. All drafts and posts that have already been created are automatically synchronized to my Obsidian vault, which also lives on OneDrive, using SyncBack Pro whenever changes are made. On Android, OneSync takes care of this job. This means that I also have everything automatically on my smartphone.
For completely new posts, I have a special folder in Obsidian called 21.11 ToBlog and a suitable template for my Hexo structure. Back at my computer, all I have to do is create a post in the command line with a photo and all the extras and copy the content into Markdown.
For my notes, I even use an Obsidian-first strategy: Using the plugin QuickAdd and a corresponding template, I create a new notes file in the synchronized notes folder source/_notes and then only have to execute the Git commit on my computer to publish the note. There are five types of notes: Standard, #TIL, Like, Bookmark, and Reply. They differ only slightly in the FrontMatter, but become important later when the WebMentions are automatically sent during the build. For notes that are still in the rough, I have a folder called 21.12 ToNote in Obsidian, where I can store things that I’m not ready to publish yet, because not everything I write ends up seeing the light of day.
Language
Obviously, English is not my native language, especially if you look at my posts up until around 2023. They are a little stiff for my taste and not particularly rich in vocabulary. I do have my own feeling for the language, but it’s based on school English and not enough practice. To make life easier for my readers (and myself), I currently write all my posts in my own language, German, and then run them through DeepL, an AI-powered translation app from Cologne. I then adapt the results to my own sense of language and hope that it sounds reasonably good. For certain posts, I also create a language variant of the German text on the blog.
Special Pages
Pages
In addition to posts, Hexo also has simple Pages without any time reference or other processed meta information. These pages are not listed on any overview page; instead, the Markdown is simply generated as it is. An example of this in this blog would be the 404 error page.
Anything Pages
Hexo includes overview pages of posts based on the date on the start page and in the archive, as well as one overview page each for categories and tags, both of which I do not use.
To generate additional indexes based on my posts meta information, I use my own project Hexo Generator Anything, which is a fork of the long-standing Hexo Index Anything. This allows me to configure overview pages of posts dedicated to a specific topic, such as my Series or my Projects. In general, two pages are created: a MAIN INDEX page with a list of the available values of a meta entry (such as an overview of all my projects) and a POSTS INDEX page with the list of posts for a specific value (such as the posts for a project). Since version 2.0, Anything can also be configured to omit the MAIN PAGE, to pick just a few posts for an overview page, like my Defaults page. Each Anything page requires an additional MD file with an introductory text and some metadata on how it should be generated.
Dynamic Pages
For all cases where Markdown is not enough or certain data must be collected beforehand or pulled from external sources, I have introduced so-called Dynamic Pages in my blog. These always consist of four parts:
- Markdown file, for introductory text and meta data
- On-Generate-Before script, to prepare all necessary data
- EJS template, to convert the data into HTML
- Generator script, to generate the page on build
I also use this page type when client-side JavaScript is required on the page to avoid having to embed scripts in Markdown. They are better placed in the EJS template, and I have significantly more options for manipulating the output as I need it.
Pretty much all of my Slash Pages are based on this four-part structure, such as the Blogroll, which processes external RSS feeds during generation, the Tiny Tools, whose data is stored in synchronized Obsidian folder structures, even the About page, which offers a few interactive gimmicks using JS, but also more complex pages such as all of my Photo pages or the Pagefind-driven Search.
Others
The Sitemap is a truly unique page, as it is not composed of traditional HTML, but rather XML, formatted according to the sitemaps.org protocol and simply dressed up a bit using XSL. As a result, it does not really match the look and feel of the other pages on this blog. However, this makes it uncompromisingly machine-readable.