How to use optimized, responsive and lazyloading images in a nuxt project

Photo by chuttersnap on Unsplash

So the goal was to get this blog super fast by using static pages and hosting it on netlify to make use of their CDN. So after getting Nuxt to work with markdown and render all pages as described here I uploaded the files to netlify - eager to see the site's speed.

Imagine my face when I saw a big red, devastating 22% on GT-Metrix.

On closer inspection I found out that the website was actually well up in the high 90ies for every aspect but for images. The rating was brought down exclusively by these two factors:

  • Serve scaled images
  • Optimize images

Lazyloading and optimization to the rescue

So obviously I had to do something. In other projects I tend to use a service like imgx for ease of image handling but this seemed overkill to implement in a simple blog like this. Also CDN already comes with netlify. I wanted a way to kinda automatically use a srcset to serve optimized images and also lazy load images on the page. So I dived into the vast world of webpack/nuxt/npm packages to find something that would solve my issue.

Lazyloading images in nuxt

Lazyloading was actually fairly easy to implement. The package I found working best is lazysizes. Simply install it with you package manager of choice:

npm install lazysizes
// or
yarn add lazysizes

Then create a nuxt plugin for it in the /plugins folder. Make sure to add .client.js to the filename to make sure it's run on the client side:

// lazysizes.client.js
import lazySizes from 'lazysizes'
export default lazySizes

No simply include this in the plugins sections of your nuxt.config.js and make sure data-src attributes get handled on build:

plugins: [
  '~/plugins/lazysizes.client.js'
],
build: {
  extend (config, { isDev, isClient, loaders: { vue } }) {
    if (isClient) {
      vue.transformAssetUrls.img = ['data-src', 'src']
      vue.transformAssetUrls.source = ['data-srcset', 'srcset']
    }
  }
}

That's it. If you now simply add the .lazyload class to any image tag it should work out of the box! (If you store your images in the assets folder)

<template>
  <img data-src="~assets/images/awesomeimage.jpg" class="lazyload">
</template>

Just lazyloading shot up the GT-Metrix rating into the orange area of around 70-80% I think. But the "Optimize images" section was still very poor. So let's tackle this next.

Automatically serve optimized, responsive images with nuxt

This part was a lot trickier and I had to try a few packages before I got it to work smoothly. Of course one can always make a big difference with carefully pre-optimizing the images and converting them to a certain size. But responsiveness will always be tricky with this and also this blog is supposed to be easy to use so I wanted to automate this as much as possible. The package that worked for me in the end is nuxt-responsive-loader

Let's start off by installing it:

npm install nuxt-responsive-loader
// or
yarn add nuxt-responsive-loader

Then include the module in your nuxt.config.js:

modules: ['nuxt-responsive-loader']

This should be enough to make it work. Try it in the markup by using:

<template>
  <img :data-src="'~/assets/images/awesomeimage.jpg" :data-srcset="require('~/assets/images/awesomeimage.jpg).srcSet" class="lazyload">
</template>

See how it combines well with the data prefix and class of the lazyloading above. Use normal :srcand srcset if you don't want/have that.

While this should work out of the box I had a few issues with the setup:

  • Image sizes did not really fit my needs
  • Image names where random hashes which is not cool for SEO
  • building the project took long and sometimes felt like it's clogging up
  • Browser was picking up the wrong image sizes
  • images in the blog content where not effected

Optimize image sizes and names

Luckily the first two points where easy to adjust in the plugins settings in nuxt.config.js:

...
responsiveLoader: {
  name: 'images/[name]-[width].[ext]', // use [name] to keep the original filename
  sizes: [320, 640, 768, 960, 1024, 1280, 1600, 1920], // array of image sizes - adjust to your layout needs
  quality: 85 // 85 is default. Tweak this if you need to
...

Make nuxt build process faster

As for the building speed nuxt-responsive-loader allows the use of two image processers. By default it uses jimp. If you feel like this makes your build too slow I highly recommend trying the other one sharp. Simply npm i sharp and set it in the config file:

...
responsiveLoader: {
  adapter: require('responsive-loader/sharp'),
...

It is still slower than without the optimization but bareable. If anybody knows how to skip the optimization in development let me know! ;)

Make browser use the right image sizes

The next challenge I had was mainly due to my layout: because of the sidebar menu browsers would pick up the wrong size of the srcset images. They always use the full window width. After some googling around I found the answer: the sizes attribute for the <img/> tag.

Here is how it works in my case for the featured image:

<img sizes="(min-width: 768px) 60vw, 95vw"/>

Basically you can give it some css formats. In my case the sidebar only appears for screens bigger than 768px. So for everything bigger than that this tells the image to use 60vw to pick the image size else default to 95vw. Pretty straight forward - however it took some trial and error to find the correct values I needed.

Apply lazyloading and optimization to images in markdown content

I still haven't solved this part. There are a few ideas:

  • parse the html to replace attributes, links and classes,
  • use tailwindcss @applyto apply the .lazyload class to all images in the content
  • or custommize somthing with the markdownIt setting of `frontmatter-markdown-loader``

I'll update here as soon as I've cracked it!

The result

I'm quite happy with what I got out of these two improvements:

GT Metrix derkinzi.de

There is always something more to optimize but for now this gets me in the range where I want to be.