I wouldn't have written this post if standard image optimization flow hadn't been so tough to me. At first, all I wanted is to apply my favourite lazysizes
to the app I'm developing now. I was stuck in this concept of modules and plugins and couldn't figure out what it actually is. Next step was image optimization and transforming current PNGs and JPEGs into WebPs — and that became a pain in the ass.
We all read posts like this to see a working example, so here it is!
Inject lazysizes
npm i lazysizes
— no need to explain.
Then create a vue-lazysizes.client.js
file and put it into @/plugins/
folder. .client
in the name signalizes server that this plugin shouldn't be run on server — it's important! This file shoud contain just two lines:
import lazySizes from 'lazysizes'
export default lazySizes
Configure nuxt.config.js
:
module.exports = {
build: {
extend (config, { isDev, isClient, loaders: { vue } }) {
if (isClient) {
vue.transformAssetUrls.img = ['data-src', 'src']
vue.transformAssetUrls.source = ['data-srcset', 'srcset']
}
}
},
plugins: [
'~/plugins/vue-lazysizes.client.js'
]
}
You can see that our "plugin" is inserted and in build
key we extend Webpack config, so vue-loader can transform aliases in data-srcset
, data-src
and srcset
attributes in <img>
and <source>
tags — we'll need it in future.
That's all! Now you can make images "lazy" as you usually do:
<figure class="picture">
<img data-src="~assets/images/image.png" class="lazyload" alt="Alternate text for the image">
</figure>
Image optimization & convertation to WebP
But what about making images smaller and loading WebP images + fallback? After hours of struggling and many attempts with whole bunch of npm-packages I found brilliant module nuxt-optimized-images
. I prefer to use mozjpeg
and pngquant
for JPEG and PNG optimization respectively.
And this is how it should be cooked:
npm i @bazzite/nuxt-optimized-images imagemin-mozjpeg imagemin-pngquant webp-loader --save-dev
— installation of dependencies.
Configure nuxt.config.js
:
module.exports = {
modules: [
'@bazzite/nuxt-optimized-images',
],
optimizedImages: {
inlineImageLimit: -1,
handleImages: ['jpeg', 'png', 'svg', 'webp', 'gif'],
optimizeImages: true,
optimizeImagesInDev: false,
defaultImageLoader: 'img-loader',
mozjpeg: {
quality: 85
},
optipng: false,
pngquant: {
speed: 7,
quality: [0.65, 0.8]
},
webp: {
quality: 85
}
}
}
Settings in optimizedImages
key are arguable, but this is my choice.
After all these manipulations you can use this snippet finally:
<figure class="picture">
<picture>
<source data-srcset="~assets/images/image.png?webp" type="image/webp">
<source data-srcset="~assets/images/image.png" type="image/png">
<img data-src="~assets/images/image.png" class="lazyload" alt="Alternate text for the image">
</picture>
</figure>
Note that WebP image source is added with ?webp
.
With this great module you can go further and create, for instance, small SVG-fallback and use it while big image is loading. You can create responsive images, generate fallback color and (probably) much more. But let it be your homework for today.
Thanks for reading and I hope you'll find this post with Google before you spend couple of days on experiments like me.
Top comments (28)
Great post! However, I cannot get this to work in my local project. Indeed, even though I included the webpack code, my images are not getting handled properly and I get a 404 error. stackoverflow.com/questions/643033...
It seems that you didn't insert lazysizes as a plugin - it's an important part.
Forgot to specify that part, you're right, but it is configured as you suggested. I have "lazysizes": "^5.2.2" in my package.json.
nuxt.config.js
plugins/lazysizes.client.js
I assume that lazysizes is loaded correctly because my final html tag looks like that, I have a
lazyloaded
class.Original tag is
Well, the only difference I can notice is that you start path in
img
from~/assets
, not~assets
. Also attrloading=lazy
looks like an overkill, I'd rather lean or on lazysizes, or on native browser's API. Or am I missing smth?I thought that lazysizes acted as a plugin for browsers that don't support the
loading
attribute. The extra slash might be due to different Nuxt versions but it's standard for now : nuxtjs.org/guide/assets/. I'm wondering why my image URL isn't interpreted by vue-loader/webpack even though I specified thetransformAssetUrls
configuration. It seems to be partially taken into account because thesrc
tag is appended to my image, even though I only specified adata-src
attribute... I'm using astatic
target for my Nuxt project but that shouldn't interfere with configuration here.Okay, can you try to put path to
src
directly, not todata-src
? Just to check that with assets everything is fine by default. Maybe issue is out of scope of this tutorial :)I removed the
loading
attribute as you suggested and changeddata-src
tosrc
and my image is loading fine, the path is different as you can see.I finally found the solution thanks to this article ! medium.com/@dannjb/a-lazy-loading-.... I was doing a check on the
isClient
property before setting thetransformAssetUrls
onvue-loader
. Problem is, it wouldn't work for SSR, maybe it will help someone that comes here in the future.Thanks, i use yarn generate and it worked without the if (isClient)
![code](https://res.cloudinary.com/practicaldev/image/fetch/s--sz-nZy11--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5i3ce2v5jn3tagas420z.png)
i also used ~/ instead of just ~
![code](https://res.cloudinary.com/practicaldev/image/fetch/s--3_5cX9v4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/jscad4s2uopkfss4x6b4.png)
Thanks for the post. Especially the combination with
nuxt-optimized-images
.I have two question please:
vue-lazyload
for your project?For me
lazysizes
is like industry standard library, well documented, predictable and SEO-friendly. Also, it supportspicture
+source
(probably,vue-lazyload
too, but this behaviour is not described directly). I won't describe all pros aboutlazysizes
, you can check it yourself.Sadly I can't tell you about events handling, bc I never met a situation when I had to handle one. Though, lazysizes do support them AFAIK.
Hey! I totally agree on the
predictable
part. I tried also some lazyload libs with nuxt and your proposal was the only one which worked out of the box. I made some additions including lqip blur up and aspect ratio for images in this repository github.com/regenrek/nuxt-lazysizes...Really great to see your job, hope it will save some time for next generations. Take my star :)
And I'm happy to know that it helped you!
Hi,
This looks great. I am either misunderstanding (highly likely), or I've made a mistake (even more likely). After implementation of the above, I no longer see images whilst on Dev. This will be because I have changed all my
src
attributes todata-src
. Is this the expected behaviour?Hello! At first, are there any console errors or warnings? Did you implement webpack settings, so when you look into source code, you see correct path to images?
I have the same error.
I have no images anymore...
Image Code:
<img data-src="~/assets/chat-icon.svg" alt="" class="chat-icon mr-2 lazyload">
Error in Browser Console:
GET localhost:3000/~/assets/chat-icon.svg 404 (Not Found)
getting a blank 1x1 image aswell
If I want to dynamically set the image path, then it is crashing with the following error:
Error: Cannot find module './path-to/image.jpg?resize&size=750&format=webp'
at webpackContextResolve (eval at ./assets/images sync recursive
My code:
Where this.src is a prop. It is all working without the suffix parameters. But when I add the suffix (thus, the nuxt-optimised-images params), the code fails and I get this error.
I have been on this for days. I just cannot fix this issue and I am willing to learn more about my problem but I don't know where to look. I hope someone can clarify this to me, since I think this dynamic declaration is essential.
I don't work with Vue now, so unfortunately I can't reproduce and examine your code. Though, I have an opinion that adding suffix dynamically might be impossible due to nature of methods. Methods are client-side, they apply in runtime, so creation of webp images with specific size cannot be done, 'cause this is a server-side action.
this was golden! great write up. extremely helpful and exactly what I needed. Thank you!
thanks for appreciation :)
Thank man 😍. Now I knew why you use
lazysizes
overvue-lazyload
. For now, It's doesn't support tag & source tag lol. Thank you one more!you’re welcome, man
Awesome
A great article..! Work like a charm. =D Thanks, man.
@alex any idea how to lazyload background image for inline style?
I never needed this option, but there is possibility to handle it with lazysizes: github.com/aFarkas/lazysizes#js-ap...