Optimizing the hell out of my website

I considered my old website to be pretty minimal. It is mostly static and includes almost no JavaScript, but I wanted to go extreme.

Screenshot of my old website.

Previously, my site weighed in at a pretty respectable 1.82 MB. That is not bad, and the size mostly comes from fonts which are easily cached. However, in my testing, it did take around 300 ms to load. That is not optimal IMO, so I want to cut down the number of requests in addition to the size.

Switching away from Hugo

Hugo has served as my static site generator of choice for a while now, and it has done it admirably. It does not contribute in any part to the size of my website (in fact, it actually has handy minimization techniques built-in.) However, I need greater flexibility in the steps I鈥檓 going to do to reduce my website鈥檚 footprint. Additionally, I wanted a more Unix-y and minimal feel to my site鈥檚 build process. Therefore, I鈥檓 going to make my own system, composed of regular Unix tools a small self-made program.

Inspired by mkws, I created sss. It is a small program that will take a directory of source files, pipe them through a collection of POSIX shell templates and create some output files. You can read more about how it works in it鈥檚 README, but it essentially just does some clever string interpolation. I was surprised at how quickly I got my Hugo setup converted to sss, and quickly proceeded to size optimization.

Going small

These are the steps I took in order to get my page as small as possible. The sizes reported are on-disk and not gzipped, so they only give an indication of how much I鈥檝e saved.

Images

Images account for about half the size of the median web site1. Simple optimization could reduce that by quite alot2. We want to go extreme, which will involve the following process:

  1. Start with a PNG file and optimize it with optipng, using the highest optimization settings (-o7.)
  2. Convert the optimized PNG into WebP with the webp tools.

I did this with the following simple shell script, to automate the process for me:

#!/bin/sh

# Optimize PNG (make backup)
optipng -- -backup -o7 $1

# Convert into WebP
cwebp $1 -q $2 -o "${1%%.png}.webp"

# Remove original file
rm $1

The first parameter is the image file we want to optimize, and the second is the quality we want to use in the WebP compression (0-100 in increasing order of quality.)

The screenshot above had the initial size of ~1.2 MB, but the final optimized image ended up being ~22 kB. Quite a drastic improvement, if you ask me.

KaTeX

Math notation is very important for me, so KaTeX is a must. It is by no means big, but since I鈥檓 doing the Markdown rendering with Pandoc - which natively detects inline and display math - I figured I could get the rendering done at build time to save having to include the JavaScript library. This was as easy as using the Pandoc filter pandoc-katex (which luckily is available in nixpkgs.)

From there, I downloaded the KaTeX stylesheet and the corresponding fonts, in order to let me self-host them and save a few HTTP requests. We鈥檙e living in the modern days now, so I deleted everything except the woff2 fonts. Additionally, I went over the stylesheet and removed every redundant rule which I am not in need of (e.g.聽the \includegraphics support.)

This saved me ~600 kB worth of JavaScript and ~1 MB worth of fonts. However, the fonts don鈥檛 really count, since they are lazily loaded.

Fonts

Speaking of fonts, I would have to give up my beloved Inter. Instead, I鈥檓 opting at trusing the web browser and simply using the default fonts. I鈥檓 giving up a bit of predictability, but it is much better for the end user, as they can adapt the font choice to their preferences.

Once again considering the KaTeX fonts, I opted for setting font-display: swap for all of the fonts. This instructs the browser to actually render the markup, even if the fonts have not loaded yet, instead of blocking. This might lead to a bit of yank, but it ensures that the content is prioritized before the layout.

Removing Inter saved me ~800 kB, and I gained a bit of rendering speed by setting font-display: swap.

Stylesheets

I want my page to look the same everywhere, so like any good netizen, I use Normalize.css. To make it even more minimal, I removed any of the rules I鈥檓 never going to use (like forms and such), and inlined it into my main stylesheet. Speaking of my main stylesheet, I did as much as I could to keep it as short as possible, removing any fancy animations or stylings of stuff I were not planning on using anytime soon.

The stylesheet is now pretty optimized, so it can鈥檛 really be prioritized enough. For that reason, I decided that I wanted to inline everything in a <style> tag for every HTML file (except KaTeX, for caching reasons). Although this involves a (very small) penalty on the document size, it is far outweighed by the gains of not doing that extra GET request.

Lastly, I stumbled upon Lightning CSS and opted on using it to minify everything as much as possible. To me, it seems like it is the best CSS minimizer that鈥檚 currently available in nixpkgs.

The minimization saved me ~5 kB, and inlining the normalization and main stylesheet saved me two requests.

Minimization

Last, but not least, we arrive at minimizing the actual HTML file itself. There are a lot of interesting things which can be done to strip down byte after byte, like not closing some tags3, stripping out <html>, <head>, <body>, etc.4 and much more. This can quickly get tiresome to do manually, though, which is why we have minimizers. I found this one during my research, and after some testing, it gave me the best (smallest) results.

The pages are quite small to begin with, so HTML minimization only accounted for ~1 kB per page. Every byte counts, though!

Favicon

One extra step I really wanted to do, which does not contribute a lot, but is fun nonetheless, is to change the favicon to something smaller. Since SVG is an XML language which supports CSS and text tags, you can make an icon that just holds a single emoji5, which I presume is the most minimal you can get - unless you want to use a single square or something as your favicon. Just insert the following into your head, and exchange the emoji with whatever you prefer:

<link rel="icon" href='data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>馃挱</text></svg>'>

The added advantage here, is that emojis look good on both light and dark mode, so you don鈥檛 need any clever media queries to make the favicon adapt to the device鈥檚 theme.

The differences here were in the range of single bytes, so I won鈥檛 bother you with any numbers.

Closing remarks

With the incredible networks speeds we currently have, all of this will likely be considered as unecessary by many. I would however implore you to think differently about it:

Take a second to think about the amount of visits bigger newspapers, social media platforms and the like get every day. Then think about the amount of data that is shipped to the end user that could be optimized, is unecessary or never even used. We could save a lot of bandwidth, computing power and subsequently energy by focusing on cutting out the cruft. As developers, we should serve as inspiration for this movement and implement this thinking into our own projects.

In some sense, we鈥檙e saving the world.

Here are some posts from sites I follow

Magit opening on a random window

If everything goes south and I find a better replacement for GNU Emacs, Magit1 would definitely be the hardest to replace. It is deeply ingrained in my daily workflow and I can鈥檛 imagine using Git with anything else. Having said that, I always hated how it鈥

via glorifiedgluer November 14, 2023

Can I be on your podcast?

I am working on rousing the Hare community to get the word out about our work. I have drafted the Hare evangelism guidelines to this effect, which summarizes how we want to see our community bringing Hare to more people. We鈥檇 like to spread the word in a way 鈥

via Drew DeVault's blog November 9, 2023

New Features Roll Call: Fall 2023

You should be able to communicate without worrying that every gossip tidbit, meme, or joke you share and who you share it with will get churned up into fodder for targeted ads or used to train an AI model. You also shouldn鈥檛 have to strip 鈥

via Signal Blog November 8, 2023