Optimized image loading in HTML and Markdown

  • This is the third article about image optimization I implemented in my blog on Nuxt with Netlify CMS.

    Check previous ones if you haven’t already:

    1. Image optimization service for Netlify CMS and Nuxt - about how I have chosen to use Netlify Large Media for that.
    2. Fixing Netlify Large Media install - about my terrifying experience setting up Netlify Large Media.

    In this article, I’m going to talk about how I implemented all this on my website. What worked and what didn’t.

    Images in HTML

    Let’s start with the images set using HTML.

    This is how it’s done with me:

    <img
      src="/img/sample_image.png?nf_resize=fit&w=250"
      loading="lazy"
      alt=". . ."
    />
    

    Loading optimized images from Netlify Large Media

    I add ?nf_resize&w=250 to load an image with a width reduced to 250 pixels and proportional heigh.

    Why I chose 250px? I checked what is the maximum width of the particular image when it’s rendered and set the width accordingly.

    On my site, images usually are shown in different sizes. For desktop browsers, it’s fixed width. On mobile, it’s wired to screen width, so it could range from 30px to 236px (something like this), so I simply chose 250px since it never gets bigger than this.

    Additional parameters that can be specified on image request from Netlify Large Media you may find in docs.

    Lazy loading

    Adding loading="lazy" say that browser should load an image only when it’s close to the viewable viewport.

    When I didn’t set that at the beginning, the page loaded in the following way: on page open, browser paints the first HTML and starts loading all images used on the current page and until it finishes page are shown as loading. If the page contains many pictures, that take time. Sometimes a lot.

    When you set loading="lazy" browser loads only images that are in the viewable part of the page and in the area of 1250px-2500px down it (doc). At least on Chromium browsers it should work this way.

    So, yeah, it could speed up page load a lot, since loading first 3 images and then others, as needed, is faster than loading more at once😉.

    However, I didn’t set this for the first images on the pages, as advised officially here. These images are anyway going to be loaded on the page open🙂.

    Trying srcset and sizes

    I also tried to do responsive image loading with the following code from Mozilla:

    <img srcset="path_to_image.png?nf_resize=fit&w=180 180w,
                 path_to_image.png?nf_resize=fit&w=250 250w"
         sizes="(max-width: 400px) 180px,
                250px"
         src="path_to_image.png?nf_resize=fit&w=250"
         alt=". . ."
    />
    

    As from code, it means that on screens with a width up to 400 pixels browser should request an image with this parameters: ?nf_resize=fit&w=180 180w. Hence with a width of 180 pixels. While on screens with a width of more than 400 pixels, it should load an image with this parameters: ?nf_resize=fit&w=250 250w. So the width should be 250 pixels.

    But these didn’t work.

    When specifying sizes in the percentage of vw (viewport), everything works, as you will see later on with Markdown images. But with px specifications nothing worked. Images were always loading with this parameters: ?nf_resize=fit&w=250 250w🤷‍♂️.

    After playing with it for some time, I understood that I could simply leave it with one ?nf_resize=fit&w=250 250w parameter. As I had before, like this:

    <img
      src="/img/sample_image.png?nf_resize=fit&w=250"
      loading="lazy"
      alt=". . ."
    />
    

    My images become really responsive on mobile, and figuring out correct vw for different layouts is a considerable pain (at least for me).

    I have also downloaded images to compare their sizes. 250px one was 114 kb, where 180px one was 63,3 kb. Quite twice a difference, but after running Page Speed tests with 180px image, I didn’t see any improvements🤷‍♂️.

    Loading optimized images from Markdown

    Doing all this optimization for blog posts is a bit more complicated.

    All my posts are written in Markdown and gets converted into HTML by markdown-it plugin.

    Markdown language has some specifications and limitations on how images are described. Here is the structure of the image specification: ![Alt text people will see if picture can’t be seen](https://link.of/image “Title to show under the image”). Not so many things we could specify. Luckily we can do a lot of modification on how Markdown is translated to HTML with the additional markdown-it- plugins.

    Lazy images

    First of all, I found and added a plugin that adds loading=”lazy” to every image that is rendered by markdown-it.

    Here is it: markdown-it-image-lazy-loading. ! If you are also planning to load optimized images by adding URL parameters to images, then wait a bit before adding it. There is a way to use only the plugin I will show next without the need to install this one. Just proceed to the next section.

    After setting loading="lazy" loading speed of blog pages with many images rocketed. This attribute is really must-have. Check the results in the next article.

    Images with srcset and sizes

    Then I tried adding markdown-it-responsive package that should add srcset and sizes attributes to every image, but this didn’t work at all. I have been getting HTML rendering error and broken pages☹️.

    After some additional search, I have found this plugin: markdown-it-modify-token. After checking it, I understood that it would work great, and I can do everything I need with it.

    Some time and this code was born:

    modifyToken: function(token, env) {
      switch (token.type) {
        case "image":
          token.attrObj.srcset =
            `${token.attrObj.src}?nf_resize=fit&w=300 300w, ` +
            `${token.attrObj.src}?nf_resize=fit&w=600 600w`;
          token.attrObj.src = token.attrObj.src + "?nf_resize=fit&w=600";
          break;
      }
    },
    

    By specifying srcset this way I’m saying to the browser: here is two images, with the width of 300px and 600px, decide by yourself what image to load according to 100% viewport width.

    First I also added size attribute, this way:

    token.attrObj.sizes = “100vw”;
    

    But removed it after reading Chris Coyier article. It’s the default value for the browser, so no need to specify that additionally.

    It works, but the behaviour is a bit strange (at least for me). When screen width is 200px browser load image with 300px width, but when screen width is set to 250px image with 600px width is loaded… I don’t understand that😐.

    And again specifying sizes in px only led to 600px image to be loaded…

    Here is code I have tried:

    token.attrObj.sizes = "(max-width: 400px) 300px, 600px"
    

    Ok, I will just leave sizes as 100vw and let the browser decide when to load what. Hope browser is smart.

    As I wrote before, the usage of markdown-it-image-lazy-loading plugin could be dropped here for additional code in

    modifyToken: function(token, env) {
    
    }
    

    Just add this:

    token.attrObj.loading = "lazy";
    

    Here, in case "image"::

    switch (token.type) {
      case "image":
        token.attrObj.srcset =
          `${token.attrObj.src}?nf_resize=fit&w=300 300w, ` +
          `${token.attrObj.src}?nf_resize=fit&w=600 600w`;
        token.attrObj.src = token.attrObj.src + "?nf_resize=fit&w=600";
              
        // This will make all images loading lazy
        token.attrObj.loading = "lazy";
        break;
    }
    

    By the way, if you want to always load images with one size, just remove srcset setter. It would look this way:

    switch (token.type) {
      case "image":
        token.attrObj.src = token.attrObj.src + "?nf_resize=fit&w=600";
        // token.attrObj.loading = "lazy";
        break;
    }
    

    As you remember, it’s better to have the first images without lazy load, but it’s a bit hard to do with images from Markdown. Additional logic should be written for markdown-it, and the time investment is not really worth it for me. Yes, there is a small drop a performance, as guys from Google say, but it won’t break the bank, I think.

    That’s all I have done for better image loading on my site. In the next article, you could check page speed improvements I got. They are quite solid😉.

  • Don't think you will look foolish in your friends' eyes, check how your share post will look like👇
  • Date published: September 4, 2020