This website has been text-oriented for a long time now. The decision to add article banners and thumbnails is a recent one. I stayed away from images for a long time since handling and optimising images can be a PITA. I was glad to find that optipng, jpegoptim, and imagemagick already exist to solve this problem for me.

My plan is as follows.

I already have all article images in my static/images folder. From there, I want to generate two copies of all PNG and JPG images. The first would be a cropped thumbnail version measuring 422-by-316. The second would be a larger banner version measuring 1024x768.

Both copies, the thumbnail and the banner, will be in folders of their own. I’ll then leverage Jekyll’s custom variables for the folder paths.

Installing the binaries

On my Mac OS X, installing the binaries required a single brew command.

brew install optipng jpegoptim imagemagick

Creating folders for thumbnails and banners

Next, I’ll create new folders under static/images. Thumbnails will go in img-thumbs and banners will go in img-normal.

cd static/images
mkdir -p img-thumbs img-normal

With the folders created, I’ll first copy all GIF, SVG, JPG, and PNG files to both folders. I’ll use the GIFs and SVGs as-is for thumbnails and banner images.

cp content/*.gif img-thumbs/; cp content/*.gif img-normal/
cp content/*.svg img-thumbs/; cp content/*.svg img-normal/
cp content/*.jpg img-thumbs/; cp content/*.jpg img-normal/
cp content/*.png img-thumbs/; cp content/*.png img-normal/

Processing thumbnails

First let’s resize and optimise the thumbnails. As mentioned earlier, I want the thumbnails to be 422-by-316. I’ll use the mogrify command from ImageMagick to resize the JPGs and PNGs.

cd img-thumbs
mogrify -resize 422x316 *.png
mogrify -format jpg -resize 422x316 *.jpg

Now let’s optimise the PNGs using optipng and the JPGs using jpegoptim.

for i in *.png; do optipng -o5 -quiet "$i"; done
jpegoptim -sq *.jpg

In the above command:

  1. For optipng, -o5 swtich sets the level of optimisation, with zero being the lowest.
  2. For jpegoptim, -s strips all image metadata, and -q sets quiet mode.

Processing banners

I’ll process the banner images like I processed the thumbnails above. Other than the file dimensions everything else will stay the same.

cd ..
cd img-normal
mogrify -resize 1024x768 *.png
mogrify -format jpg -resize 1024x768 *.jpg
for i in *.png; do optipng -o5 -quiet "$i"; done
jpegoptim -sq *.jpg

Configuring the paths in Jekyll

img-thumbs now contains my thumbnails and img-normal contains the banners. To make my life easier, I’ll set both of them to Jekyll variables in the _config.yml.

content-images-path: /static/images/img-normal/
content-thumbs-images-path: /static/images/img-thumbs/

Using these is simple. When I want to display the thumbnail I’ll prepend content-thumbs-images-path to the image. When I want to display the full banner I’ll prepend content-images-path.

For example, articles on this site now have a banner_img property in the front matter. The value of this property is the name of the image file. To display the thumbnail for the article, I use the following code:

{% if page.banner_img %}
 <img src="{{ page.banner_img | prepend: site.content-images-path | prepend: site.baseurl | prepend: site.url }}" alt="Banner image for {{ page.title }}" />
{% endif %}


There are several improvements we can make to the commands above. The most obvious might be to use rsync to only copy changed files to img-thumbs and img-normal. That way we’re not re-processing files over and over again. Another improvement might be to add those commands to Git pre-commit hooks or a CI pipeline.

Resizing and optimising images to reduce their size is a win for the user and the web as a whole. Fewer bytes transmitted over the wire means a lower carbon footprint, but that’s another article. The UX victory is good enough for now :)

Happy coding :)