SEO and Performance Optimisation with Nuxt Image

By Thomas Findlay 6 Jul, 2021

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.

Initial network 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/.

Initial Lighthouse score

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.

nuxt-img images network load

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.

Images with sizes prop

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.

Lighthouse score after adding sizes prop

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!

Quality reduction and lazy loading score

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.

Nuxt Picture

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.

Improved SEO score

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.


Want to learn something new?

Subscribe to the newsletter!


Thomas Findlay photo

About author

Thomas Findlay is a 5 star rated mentor, full-stack developer, consultant, and technical writer. He works with many different technologies such as JavaScript, Vue, React, React Native, Node.js, Python, PHP, and more. He has obtained MSc in Advanced Computer Science degree with Distinction at Exeter University, as well as First-Class BSc in Web Design & Development at Northumbria University.

Over the years, Thomas has worked with many developers and teams from beginners to advanced and helped them build and scale their applications and products. He also mentored a lot of developers and students, and helped them progress in their careers.

To get to know more about Thomas you can check out his Codementor and Twitter profiles.