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

Faster linking times on nightly on Linux using `rust-lld`

TL;DR: rustc will use rust-lld by default on x86_64-unknown-linux-gnu on nightly to significantly reduce linking times. Some context Linking time is often a big part of compilation time. When rustc needs to build a binary or a shared library, it will usually 鈥

via Rust Blog May 17, 2024

The Small Web and Science

Over the past few years I鈥檝e been noticing more and more of a discourse about the 鈥渟mall web鈥 in my online communities. It鈥檚 cool to see! I mentioned the concept in an earlier post and thought I might write a few more words about it today. As far as I can tel鈥

via PKGW May 14, 2024

Music Assistant 2.0: Your Music, Your Players

Today, exactly five years ago, I, Marcel, started working on Music Assistant . What began as a quick script, to sync my playlists so I could switch between streaming providers, grew into a beast on its own. Music Assistant is what I鈥檇 like to call a 鈥渕usic鈥

via Home Assistant May 9, 2024