I considered my old website to be pretty minimal. It is mostly static and includes almost no JavaScript, but I wanted to go extreme.
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.
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.
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 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:
optipng
,
using the highest optimization settings (-o7
.)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.
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.
Speaking of fonts, I would have to give up my beloved Inter. Instead, I’m opting at trusing the web browser and simply using the default fonts. I’m 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
.
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.
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!
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.
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.
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’t imagine using Git with anything else. Having said that, I always hated how it…
via glorifiedgluer November 14, 2023
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’d like to spread the word in a way …
via Drew DeVault's blog November 9, 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’t have to strip …
via Signal Blog November 8, 2023