Nowadays, web applications are expected to be very fast since users often abandon websites that take too long to be ready. What's more, slow websites are often penalised by search engines, such as Google. Therefore, it's crucial for websites to load as quickly as possible. Nevertheless, throughout my career, I've seen a lot of websites that loaded massive and unoptimised images which were way too large and too heavy. In this article, I want to show you how you can improve SEO and your Nuxt app's performance by optimising images using the recently released Nuxt Image module.
Project setup
You can create a new project by running one of the commands below:
// npm
npm init nuxt-app my-nuxt-image-app
// yarn
yarn create nuxt-app my-nuxt-image-app
// npx
npx create-nuxt-app my-nuxt-image-app
After the project is created, CD into the directory and install the Nuxt Image package.
cd my-nuxt-image-app;
yarn add --dev @nuxt/image;
// or
npm install -D @nuxt/image;
When the installation is complete, open the nuxt.config.js file and add the @nuxt/image module. If your Nuxt project's target
is static
, then you need to add the @nuxt/image module to buildModules
. If it's server
, then add it to modules
. My project is targetting the former, so I added it to buildModules
.
nuxt.config.js
{
// ...other config
buildModules: [
// ...other modules
'@nuxt/image',
]
}
Now you can run your project in dev mode.
npm run dev
// or
yarn dev
That's it for the setup. Let's add some images.
Adding large images
Let's add some images to the app. I downloaded a few large images from the Unsplash website. If you want to use the same images, you can find them in this GitHub repo. Otherwise, just use your own images, but make sure they are quite big. Now, let's update the homepage file as shown below.
pages/index.vue
<template>
<div class="container">
<img src="/images/waterfall.jpg" alt="waterfall" />
<img src="/images/forest.jpg" alt="forest" />
<img src="/images/hills.jpg" alt="hills" />
<img src="/images/forest-hill.jpg" alt="forest-hill" />
<img src="/images/island.jpg" alt="island" />
<img src="/images/beach.jpg" alt="beach" />
<img src="/images/lake.jpg" alt="lake" />
</div>
</template>
<script>
export default {}
</script>
<style>
.container {
margin: 0 auto;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
text-align: center;
max-width: 1180px;
}
.container img {
display: block;
height: auto;
max-width: 100%;
object-fit: contain;
margin-top: 4rem;
}
</style>
If you run the app and check the network tab, you will see that the images are really big and take quite some time to load.
Let's have a look at what Lighthouse says. I have generated a static website by running the generate
command and deployed it to Vercel to test it in the Lighthouse.
Here is the URL for the unoptimised version - https://performance-optimisation-with-nuxt-image-q74q3isdk.vercel.app/.
As you can see on the image above, that's a really bad performance score - 37 out of 100. Images take way too long to load. Let's have a look at what we can do to improve them using the Nuxt Image plugin.
Nuxt Image
Let's change the image elements to use the nuxt-img
component.
<nuxt-img src="/images/waterfall.jpg" alt="waterfall" />
<nuxt-img src="/images/forest.jpg" alt="forest" />
<nuxt-img src="/images/hills.jpg" alt="hills" />
<nuxt-img src="/images/forest-hill.jpg" alt="forest-hill" />
<nuxt-img src="/images/island.jpg" alt="island" />
<nuxt-img src="/images/beach.jpg" alt="beach" />
<nuxt-img src="/images/lake.jpg" alt="lake" />
Switching from the native image element already resulted in an improved size, as the images are now a bit smaller. Previously, the waterfall.jpg image weighted 5.6mb, and after using the nuxt-img
component was reduced to 4.8mb.
This of course, is still too big, so let's see what else we can do.
Image size and quality reduction
The nuxt-img
component accepts a prop called sizes. We can define what sizes should the Nuxt Image plugin generate for an image. At the moment, the app is loading full-sized images. For instance, the waterfall.jpg image has a size of 4000x6000. Let's specify smaller sizes.
<template>
<div class="container">
<nuxt-img
sizes="sm:200px md:400px lg:800px"
src="/images/waterfall.jpg"
alt="waterfall"
/>
<nuxt-img
sizes="sm:200px md:400px lg:800px"
src="/images/forest.jpg"
alt="forest"
/>
<nuxt-img
sizes="sm:200px md:400px lg:800px"
src="/images/hills.jpg"
alt="hills"
/>
<nuxt-img
sizes="sm:200px md:400px lg:800px"
src="/images/forest-hill.jpg"
alt="forest-hill"
/>
<nuxt-img
sizes="sm:200px md:400px lg:800px"
src="/images/island.jpg"
alt="island"
/>
<nuxt-img
sizes="sm:200px md:400px lg:800px"
src="/images/beach.jpg"
alt="beach"
/>
<nuxt-img
sizes="sm:200px md:400px lg:800px"
src="/images/lake.jpg"
alt="lake"
/>
</div>
</template>
Below, you can see how this changed affected the network load.
For example, previously, the waterfall image had a size of 4.8 MB, whilst now it's just 297 kB. This is a massive improvement. The lighthouse looks much better as well, as the performance score improved from 37 to 78 points.
This still isn't the best though, so let's reduce quality of the images and add lazy loading.
<template>
<div class="container">
<nuxt-img
sizes="sm:200px md:400px lg:800px"
src="/images/waterfall.jpg"
alt="waterfall"
loading="lazy"
width="1024"
height="800"
quality="75"
/>
<nuxt-img
sizes="sm:200px md:400px lg:800px"
src="/images/forest.jpg"
alt="forest"
loading="lazy"
width="1024"
height="800"
quality="75"
/>
<nuxt-img
sizes="sm:200px md:400px lg:800px"
src="/images/hills.jpg"
alt="hills"
loading="lazy"
width="1024"
height="800"
quality="75"
/>
<nuxt-img
sizes="sm:200px md:400px lg:800px"
src="/images/forest-hill.jpg"
alt="forest-hill"
loading="lazy"
width="1024"
height="800"
quality="75"
/>
<nuxt-img
sizes="sm:200px md:400px lg:800px"
src="/images/island.jpg"
alt="island"
loading="lazy"
width="1024"
height="800"
quality="75"
/>
<nuxt-img
sizes="sm:200px md:400px lg:800px"
src="/images/beach.jpg"
alt="beach"
loading="lazy"
width="1024"
height="800"
quality="75"
/>
<nuxt-img
sizes="sm:200px md:400px lg:800px"
src="/images/lake.jpg"
alt="lake"
loading="lazy"
width="1024"
height="800"
quality="75"
/>
</div>
</template>
After reducing the quality and adding lazy loading, the performance score jumped to 96!
Nuxt-Picture and image formats
The nuxt-img
component serves files in the original image format. However, we can use the nuxt-picture
component to serve better formats based on the browser's support. For example, if we switch to the nuxt-picture
component and open the website in Chrome, we won't get an image in the .jpg format but rather in .webp. The API of the nuxt-picture
component is almost identical to nuxt-img
, so we don't have to add any additional props.
Lighthouse SEO score improvements
The only thing left to optimise that is recommended by Lighthouse is removing some unused JavaScript. However, we can leave it at that, as I wanted to show you how to use the Nuxt Image module. The SEO score is 77 at the moment, as I deployed the app to Vercel's staging environment, which does not allow indexing. I also did not include the robots.txt file nor description meta tag. After adding these and deploying to production, the SEO score jumped to 100 points.
You can see the final result here.
Wrap up
Nuxt Image is a great module that makes image optimisation much easier and convenient. It can help a lot with improving website loading performance and thus making SEO ranking better. It can be used with multiple image service provides, such as Cloudinary, Fastly, ImageKit, and more. You can see the whole list in the documentation.
If you would like to learn more tips, advanced patterns, techniques and best practices related to Vue, you might want to check out "Vue - The Road To Enterprise" book, sign up for the newsletter, and follow me on Twitter.