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’m going to do to reduce my website’s footprint. Additionally, I wanted a more Unix-y and minimal feel to my site’s build process. Therefore, I’m 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’s 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’ve 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’m 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’re 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’t really count, since they are lazily loaded.

Fonts

Speaking of fonts, I would have to give up my beloved Inter. Instead, I’m opting at trusing the web browser and simply setting the font to serif. This gives the page a unique look, while still not loading any extra fonts.

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’m 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’t 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’s 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’t need any clever media queries to make the favicon adapt to the device’s theme.

The differences here were in the range of single bytes, so I won’t 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’re saving the world.


  1. https://httparchive.org/reports/page-weight#bytesImg↩︎

  2. https://httparchive.org/reports/state-of-images#optimizedImages↩︎

  3. https://blog.novalistic.com/archives/2017/08/optional-end-tags-in-html/↩︎

  4. https://endtimes.dev/you-can-leave-out-html-head-and-body-tags/↩︎

  5. https://endtimes.dev/emojis-as-favicons/↩︎