Optimize Page Speed with Automatic Image Conversion and Resizing

How long do you usually wait for a web page to load? If it’s not something terribly important, I’m usually not patient enough to wait for a page to load for more than a couple of seconds at most. And, to be honest, reading blog post is not terribly important to me, most of the time.

Web pages should load as fast as possible. This means we should spend some time optimizing our web pages by minifying Javascript and CSS and making sure the images we’re using have the minimal file size possible, among other things.

In this article, we’re looking at how to automate image processing for a static site generator so that the original images in PNG or JPEG format will be automatically converted into the file-size-optimized WEBP format and re-sized to a minimal size.

Example Project

You can find the complete example discussed in this article on GitHub.

How To Measure Page Speed?

Before doing anything to our web page, we should check if there’s anything to be optimized, at all. With PageSpeed Insights, Google provides a nice tool to check the speed of our page. We can just type in an URL, and we’ll get a score ranging from 0 to 100:

Page speed is important

Below the score, there’s a breakdown of the actions we can do to improve the score. If one of the items in this list is “Serve images in next-gen formats”, the method described below will help to improve the page speed score.

Automatic Image Processing with NPM

Let’s dive into an example project to see how to set up automated image processing with NPM and Hugo. You can do the same with any other static site generator, but for this article, we’ll go with Hugo.

Project Setup

Assuming that we have already wrapped our Hugo project with NPM, we have a package.json file that controls our Javascript dependencies and build scripts, and we can run npm run build to have Hugo create our static website.

In this tutorial, we want to extend the project setup so that PNG and JPEG images in an input folder will be automatically re-sized and converted into the WEBP format and copied into a folder that we can use in our Hugo content and layouts.

We’ll have the following folder structure in the main folder of our project:

├── raw-images
|   ├── feature-image-1.jpeg
|   └── feature-image-2.png
└── static
   └── generated
     ├── full
     |   ├── feature-image-1.webp
     |   └── feature-image-2.webp
     └── preview
       ├── feature-image-1.webp
       └── feature-image-2.webp

The folder raw-images is where we put our JPEG and PNG images.

Our goal is to convert these images into the WEBP format and put the converted images into the folder static/generated. There are two subfolders here, full and preview for the full-sized version of the image and a smaller preview-sized version of the image.

Everything in the static folder will be automatically picked up by Hugo so that we can use it in our content and layouts.

I’m using this method on this blog to create a preview-sized image for the overview page and a full-sized image to be displayed in social media posts.

Installing The imagemin Package

To start, we install the imagemin NPM package, which is a Javascript package that allows us to minify and convert images:

npm install --save-dev imagemin imagemin-webp

We also installed the imagemin-webp package, which is a plugin to the imagemin package that can convert images into the WEBP format.

A Script to Convert Images to WEBP

Next, we create the Javascript file optimize-images.js that converts the images from the raw-images folder into WEBP and moves them into the static/generated/full folder:

const imagemin = require("imagemin");
const imageminWebp = require("imagemin-webp");

(async () => {
  let fullImages = await convertFullImages();
  console.log('Converted ' + fullImages.length + ' images to WEBP format.');
})();

function convertFullImages(){
  return imagemin(['raw-images/*.{jpg,png}'], {
    destination: 'static/generated/full',
      plugins: [
        imageminWebp()
      ]
  });
}

The function convertFullImages() does the work. It calls the imagemin() function with the input and output folders and uses the imageminWebp() plugin to make sure that the images are being converted into WEBP. The plugin provides a bunch of options we can define, but we’re happy with the defaults, here.

Now, all we have to do is to add this script to package.json so that we can call it from the build process:

{
  ...
  "scripts": {
  "build": "npm run images:optimize && npm run hugo:build",
  "serve": "npm run images:optimize && npm run hugo:serve",
  "hugo:build": "hugo -d build",
  "hugo:serve": "hugo server",
  "images:optimize": "node scripts/optimize-images.js",
  ...
  },
  ...
}

Now, we can run npm run images:optimize to run our optimization script and it will convert the images and put them into the destination folder.

Additionally, we can run npm run build and npm run serve and the images will be optimized before Hugo is executed. This means that Hugo will automatically pick up the converted images and doesn’t even know about the original ones.

Resizing Images

To re-size the images to a smaller preview size that we can use in overview pages, we add another function to optimize-images.js:

...

(async () => {
  let fullImages = await convertFullImages();
  let previewImages = await convertPreviewImages();
  let total = fullImages.length + previewImages.length;
  console.log('Converted ' + total + ' images to WEBP format.');
})();

...

function convertPreviewImages(){
  return imagemin(['raw-images/*.{jpg,png}'], {
    destination: 'static/generated/preview',
    plugins: [
      imageminWebp({
        resize: {
          width: 510,
          height: 267
        }
      })
    ]
  });
}

This function uses the resize option of the webp plugin to set a width and height. Also, this function puts the files into the preview folder instead of the full folder.

In the case above, I decided to resize the images to 510x267 pixels because that’s the maximum size of the preview images in the blog overview on a desktop browser. They get a little smaller on mobile devices due to the responsive design, but I didn’t want to create a separate image size for each device, so I’m going with the desktop size. That’s still a lot less than the original 1200x628 pixels!

Using the Images

In our content, we can now use the optimized images like this:

![Full size](/generated/full/feature-image-1.webp)
![Preview size](/generated/preview/feature-image-1.webp)

Similarly, we can use the images in our HTML layout templates:

<img alt="Full size" src="/generated/full/feature-image-1.webp">
<img alt="Preview size" src="/generated/preview/feature-image-1.webp">

Conclusion

This article showed a way of optimizing images with Hugo and NPM. This can be adapted for use with other static site generators, as long as we wrap it in an NPM project. Of course, it can be modified to suit any custom folders or image sizes and formats that you might for your blog, as well.

If you want to play around with a working example project, check out the code on Github.