When I started this blog in 2017, I had to decide on a system to use. A long time ago I used Wordpress, but because of the high maintenance required, as well the constant security issues, I decided to go with a static site generator instead: Hugo.

How did it go?

The start was quite easy, I found a lovely template and managed to get a site up quickly. Also thanks to the built-in preview server, it was a pleasure to write text and directly see the results in your browser.

Because I only write blog posts every few months, I tend to skip many versions of Hugo. As a result, the number of warnings in my template tend to pile up, and sometimes deprecated stuff got removed and my page broke. When this happened, I generally switched to a new themes (cause I find the template language extremly ugly and therefore didn't fix stuff myself), but then spent a lot of time defusing them: Removing tracking or other third-party resources.

I had a bit of free time over the last weeks, so I took up the challenge and built my own (closed source) blog engine in Python. It has a lot of compatibility to Hugo (generated URL structure and Markdown parsing), but gives me full control over the generation - and I have a easier time fixing bugs. Also, it was fun 😁

Getting to 100% in Lighthouse

Lighthouse is an integrated feature of the Chrome developer tools to check a website for performance or accessibility common issues. I've used it at the end of the development to fix some smaller issues.

No frameworks or libraries

The first but probably most important point: Write everything from scratch. No pre-built templates, no CSS frameworks, no huge reset stylesheets. So kinda like the 90ies.

While this takes a bit longer (I struggled quite a bit with the scrolling code boxes), the end result is the tiniest stylesheet possible: Just the CSS that is actually needed.

In the final page, the entire minified stylesheet is just 6 KiB - without even gzipping.

Compress CSS

In my templates, I use a feature flag to differentiate between development and final build. In preview mode, I load all CSS files (I split them up for easier work) by themselves, in production I merge them all together.

This is done using clean-css and a little bash script:

# Install (only run once)
npm install clean-css clean-css-cli

# Merge all CSS files
cleancss \
    --source-map \
    -O2 \
    -o public/style.min.css \
    static/styles/*_*.css \

Why the *_*.css? All CSS files are numbered (e.g. 10_base.css), expect one file that only contains dev-only styles. Through this little trick, it's excluded from merging.

Compress HTML

To save even more bytes, I compress all HTML files by stripping all whitespace and comments.

This is done using html-minifier:

# Install (only run once)
npm install html-minifier

# Process all files
for filename in public/**/*.html; do
    echo $filename

    html-minifier \
        --collapse-whitespace \
        --remove-comments \
        --remove-optional-tags \
        --remove-redundant-attributes \
        --remove-script-type-attributes \
        --output "$filename" \

A note for fellow macOS users: The ancient bash that Apple provides doesn't support the **/* syntax, I fixed this by executing my post-build script using zsh.

Adding a CSP

A Content Security Policy can be used to tell browsers where to load dynamic content from, to effectively prevent XSS. While adding one to a completely static blog is quite overkill, it doesn't actually hurt.

I've used a online generator tool for mine, and pretty much ticked "None" for most features.