SEO With Hugo (9) Images Optimisation

by Samuele Lilliu | 29 January 2023

Image optimization improves website load speed, enhances user experience, and increases visibility in search results for better SEO.

X
  • Producer: Samuele Lilliu
  • Software: Hugo, HTML, SCSS

Optimizing images for the web is an important step in ensuring that your website loads quickly and effectively. There are several ways to optimize images for the web, including using the correct file format, compressing images, and reducing image size.

One of the most important ways to optimize images for the web is to use the correct file format. The most common image formats used on the web are JPEG, PNG, and GIF. JPEG is best for photographs and images with a lot of colors, while PNG is best for images with a transparent background or images with text or graphics. GIF is best for simple graphics and animations. Using the correct file format ensures that the image will be displayed correctly and will not slow down the page loading speed.

Another way to optimize images for the web is to compress them. Compression reduces the file size of an image without affecting its quality. There are many free tools available, such as TinyJPG and Compressor.io, which allow you to easily compress your images. Compressing images can significantly reduce the file size and improve page loading speed.

Reducing image size is another important aspect of optimizing images for the web. Large images can slow down page loading speed, so it is important to ensure that the images on your website are the appropriate size. You can reduce image size by cropping and resizing the images, and removing any unnecessary elements.

Image lazy loading is a technique used in web development to delay the loading of images until they are needed. The idea behind this technique is to only load images that are visible to the user, rather than loading all images on a webpage at once. This can significantly improve the page loading speed and user experience, especially on pages with many images.

When a webpage is loaded, the browser loads the HTML, CSS, and JavaScript first, and then the images. With image lazy loading, the images are not loaded until the user scrolls down the page and the images come into view. This means that the images that are not initially visible to the user do not slow down the page loading process.

There are different ways to implement image lazy loading, including using JavaScript libraries, such as Lazy Load or Lozad.js, or using the native “loading” attribute in HTML5.

SRCSET is an HTML attribute that allows you to provide multiple versions of an image and specify which image to display based on the device’s screen size or resolution. It is used to ensure that the most appropriate image is loaded for the device and screen size that is being used to view the webpage.

The SRCSET attribute is used in conjunction with the SRC attribute, which is used to specify the default image that should be loaded. The SRCSET attribute is used to specify multiple versions of the image, along with the width or resolution of each image. The browser will then use the image that is best suited for the device and screen size that is being used to view the webpage.

For example, you might have a small thumbnail image for mobile devices and a larger image for desktop devices. The SRCSET attribute would be used to specify the different versions of the image, and the browser would automatically choose the appropriate image to display.

The SRCSET attribute is a useful tool for improving the performance of web pages, particularly on mobile devices. It can be used to ensure that the most appropriate image is loaded, which can save bandwidth and improve page loading speed.

In summary, SRCSET is an HTML attribute that allows you to specify multiple versions of an image and specify which image to display based on the device’s screen size or resolution. It is a powerful tool for improving the performance of web pages, particularly on mobile devices, by ensuring that the most appropriate image is loaded.

Implementing SRCSET in Hugo

In partials for templates

First of all we can take a look at the Hugo resources for image optimisation. This is how I have implemented SRCSET in Hugo for the equipment single pages (e.g. gopro-hero-9-kit).

We will be referring to the following file and folder structure:

folders structure

Within the equipment/single.html template we call the image-carousel.html partial:

          <div class="col-10 col-sm-8 col-lg-5 mx-auto ">
            {{ partial "image-carousel" . }}
          </div>

This is image-carousel.html:

{{ $path1 := .RelPermalink }}
<div
  id="carouselExampleControls"
  class="carousel slide "
  data-bs-ride="carousel"
>
  <div class="carousel-inner ">
    {{ range .Resources.ByType "image" }}
      <div class="carousel-item active">
        {{ partial "image" (dict "src" . "page" $ "alt" $.Title "format" "equipment") }}
      </div>
    {{ end }}
  </div>
  <button
    class="carousel-control-prev"
    type="button"
    data-bs-target="#carouselExampleControls"
    data-bs-slide="prev"
  >
    <span class="carousel-control-prev-icon" aria-hidden="true"></span>
    <span class="visually-hidden">Previous</span>
  </button>
  <button
    class="carousel-control-next"
    type="button"
    data-bs-target="#carouselExampleControls"
    data-bs-slide="next"
  >
    <span class="carousel-control-next-icon" aria-hidden="true"></span>
    <span class="visually-hidden">Next</span>
  </button>
</div>

With {{ range .Resources.ByType “image” }} we loop all the images with the gopro-hero-9-kit folder (or any other equipment folder). You can find more information on Resources.ByType here. Next, within the <div> we call the image.html partial with {{ partial "image" (dict "src" . "page" $ "alt" $.Title "format" "equipment") }}. With dict we pass an array of parameters to the partial. With . we pass the current context, which is the current image in the range iteration. With $ we pass the global context, which is the current page. $.Title is the page title and format is just a parameter to select the list of sizes for the image.

The following is the image.html partial, which takes care of the SRCSET:

{{ $src := .page.Resources.GetMatch .src }}

{{ $widths := site.Params.image.widths.default | default (slice 400 600 800 1200) }}
{{ if eq .format "equipment" }}
  {{ $widths = site.Params.image.widths.productSingle | default (slice 336 465 535 700 1000) }}
{{ end }}
{{ $widths = sort $widths }}
{{ $srcset := slice }}
{{ $img := "" }}
{{ range $index, $width := $widths }}
  {{ $resize := $src.Resize (printf "%vx" $width ) }}
  {{ $srcset = $srcset | append (printf "%s %vw" ($resize.RelPermalink | safeURL ) $width) }}
  {{ if eq (len $widths) (add $index 1) }}
    {{ $img = $resize }}
  {{ end }}

{{ end }}
{{ $srcset = delimit $srcset ", " }}

<img
  data-src="{{ $img.RelPermalink }}"
  data-srcset="{{ $srcset }}"
  class="img-fluid lazyload"
  height="{{ $img.Height }}"
  width="{{ $img.Width }}"
  alt="{{ .alt }}"
  data-sizes="auto"
/>
<noscript>
  <img
    src="{{ $img.RelPermalink }}"
    class="img-fluid"
    height="{{ $img.Height }}"
    width="{{ $img.Width }}"
    alt="{{ .alt }}"
  />
</noscript>

In the first part we use {{ $src := .page.Resources.GetMatch .src }} to get the image url. A list of image widths is assigned to the variable $widths. These values are available in the config.yaml file:

params:
  image:
    widths:
      default: [400, 600, 800, 1200]
      equipment: [336, 456, 535, 700, 1000]

With {{ $resize := $src.Resize (printf "%vx" $width ) }} we are using a Hugo command to resize the image to the specified width and/or height. Hugo wants something in the format of {{ $image := $image.Resize "600x" }}. So we use %v in the printf function to insert $width. This is repeated for all the widths in the $width array. As we loop through the widths we build up the $srcset array of strings, by appending a new element at each loop. Here with $s we add the filename $resize.RelPermalink and with %v we add the $width. We add the safeURL pipe to avoid Hugo escaping the link.

Within the <img> tags we need to specify the URL of each image data-src="{{ $img.RelPermalink }}" and the srcset data-srcset="{{ $srcset }}". We want the image to be responsive so we use the class img-fluid, but we also specify lazyload to guarantee the lazy load behaviour. Hugo also automatically calculates the hight for us, which we can specify here height="{{ $img.Height }}".

For lazyload we need to install lazysizes:

npm install lazysizes

We need to load lazysizes from assets/js/vendor.js:

import 'lazysizes';
import 'lazysizes/plugins/native-loading/ls.native-loading';

lazySizes.cfg.nativeLoading = {
  setLoadingAttribute: true,
  disableListeners: {
    scroll: true,
  },
};

The reason why we are adding data- before sizes, src, and srcset is because that’s how lazysizes wants it.

In the last part <noscript> is used in case JavaScript is disabled in the browser. For this we need to add a bit of code in assets/scss/main.scss:

.no-js img.lazyload {
  display: none;
}
img[data-sizes='auto'] {
  display: block;
  width: 100%;
}

This is how the code is rendered:

browser-inspector

The best way to check if this is working is by resizing the browser size and inspecting the size of the rendered image.

Using shortcodes inside markdown files

For images we wish to include directly in the markdown file of a page, for example the current one, I’m using the /shortcodes/image2.html shortcode (between double brackets!):

< image2 src="images/folders-structure.jpg" alt="folders structure" format="equipment" colSize="12" >

Note that here I’m skipping some of the parameters. Note also that for some reason it won’t work unless I include the path images/ with the filename.

The code for image2.html is:

{{- $src :=.Get "src" -}}
{{- $alt :=.Get "alt" -}}
{{- $format :=.Get "format" -}}
{{- $colSize := .Get "colSize" }}
{{- $captionAlign := .Get "captionAlign" }}
{{- $learnMore := .Get "learnMore" }}

{{- $src := .Page.Resources.GetMatch $src -}}
{{- $widths := site.Params.image.widths.default | default (slice 400 600 800 1200) -}}

{{- if eq $format "equipment" -}}
  {{- $widths = site.Params.image.widths.productSingle | default (slice 336 465 535 700 1000) -}}
{{- end -}}

{{- $widths = sort $widths -}}
{{- $srcset := slice -}}
{{- $img := "" -}}

{{- range $index, $width := $widths -}}
  {{- $resize := $src.Resize (printf "%vx" $width ) -}}
  {{- $srcset = $srcset | append (printf "%s %vw" ($resize.RelPermalink | safeURL ) $width) -}}
  {{- if eq (len $widths) (add $index 1) -}}
    {{- $img = $resize -}}
  {{- end -}}
{{- end -}}
{{- $srcset = delimit $srcset ", " -}}

<div class="container">
  <div class="row">
    <div class="col"></div>
    <div class="col-{{ $colSize }} ">
      <img
        data-src="{{ $img.RelPermalink }}"
        data-srcset="{{ $srcset }}"
        class="img-fluid lazyload"
        height="{{ $img.Height }}"
        width="{{ $img.Width }}"
        alt="{{ $alt }}"
        data-sizes="auto"
      />
      {{- if .Get "figureNumber" -}}
        <figcaption class="figure-caption {{ $captionAlign }} py-1">
          <span><b>{{ .Get "figureNumber" -}}</b> | </span
          >{{- .Get "caption" -}}<span
            ><a
              href="{{ $learnMore }}"
              target="_blank"
              rel="noopener noreferrer"
              >Learn more about this project.</a
            ></span
          >
                </figcaption>
      {{- end -}}
      <noscript>
        <img
          src="{{ $img.RelPermalink }}"
          class="img-fluid"
          height="{{ $img.Height }}"
          width="{{ $img.Width }}"
          alt="{{ $alt }}"
        />
      </noscript>
    </div>
    <div class="col"></div>
  </div>
</div>

<a href=""></a>

Which is basically an extension of what we’ve seen before.