CloudinaryNuxtjsVuejsTutorialsProgramming

Images optimization with Cloudinary in Nuxt apps

Aug 11, 2020 ยท 12 min read

Despite being a critical factor of site performance score, optimizing images is not an easy task to accomplish. With Cloudinary, we can achieve this at the minimum effort. How do we use Cloudinary with Nuxt, and what benefits can it bring?

Letโ€™s check out.

TL;DR

If you are already aware of Cloudinary and Nuxt and how to use to them, you can skip the article ๐Ÿ˜‰ and do the following to install Cloudinary in Nuxt application:

  • Run yarn add @nuxtjs/cloudinary or npm i โ€”save-dev @nuxtjs/cloudinary to install the module package in your Nuxt app.
  • Add @nuxtjs/cloudinary as a module of modules in nuxt.config.js
  • Add Cloudinary configurations, such as cloudName to cloudinary key field in nuxt.config.js
  • Start using this.$cloudinary() in your component to enjoy the magic of Cloudinary to your images.
  • Read the docs ๐Ÿ“„๐Ÿ˜‰

Want to know more? Letโ€™s continue, shall we?

What is image optimization, and why is it important?

Websites and apps nowadays are full of content and images. Loading large images is critical to the overall performance of site and apps, such as longer loading time, a more substantial burden to network resources, cost of data usage, and heavier pages. Page load speed is essential in SEO (Search engine optimization) and UX (User experience), thus affecting your audience engagement. Remember, you have less than 15 secs before your users decide to navigate away from your site, and that includes loading time.

Therefore, we should not take image optimization lightly. Santiago made a handy Twitter thread on what we can do as developers to optimize images, and you should check it out. The thread is long, as so is the to-do list for us developers. Luckily, we can quickly achieve some of the to-do actions by letting external solutions handle since they do it better and faster. And among these solutions is Cloudinary.

What is Cloudinary?

Cloudinary is an end-to-end solution that offers image and video handling, such as storage, run-time manipulation, and, indeed, fast delivery as a CDN with optimization out of the box. Sounds fancy, doesn't it?

To put it simply, imagine you have an image displayed in various locations within the application in different sizes and shapes:

The different requests of transformations for a single image

One straightforward approach is using CSS to resize and modify how the original image appears to users. Nevertheless, since we do not know all the size requests for this image throughout the app, and we'd like to keep it at the highest quality possible, the most straightforward approach is to keep the highest resolution version in storage and fetch it to our site on loading. Similar issues with video. Thatโ€™s when the complication and performance issue appears.

Cloudinary comes to solve this exact complication, with its on-the-fly image manipulation through URL. For example, we can use Cloudinary to resize the above cat avatar to 100x100 pixels in a round shape, in 3 simple steps:

  • Get yourself a FREE Cloudinary account (if you haven't got one yet) and grab your cloud name.

  • Upload your image to any data storage (Cloudinary or Amazon S3 - up to your preference).

  • Generate your optimized delivery URL for the image, following the below patterns:

    The Cloudinary URL format

    In which:

    • cloud-name is your Cloudinary's cloud name, which you can find in the Dashboard or the Settings page of Cloudinary app.

      How to find cloud name in Dashboard
    • transformations - a string of manipulation options (without space) to generate the desired version, based on Cloudinary standards.

    • version - auto-generated version for cache purpose from Cloudinary,

    • path-to-image - can be the path of an image stored in Cloudinary (aka public id), or the URL of an image stored in external sources (S3, for instance).

      Hence, we can compute our image's delivery URL as follows:

      https://res.cloudinary.com/mayashavin/image/upload/w_100,h_100,c_thumb,r_max/v1/examples/avatar.png
      

      With the following transformations:

    • w_100 and h_100 - 100x100 pixels in size

    • c_thumb - crop as a thumbnail with auto face detection

    • r_max - border-radius at maximum to create a round shape.

      And tada ๐ŸŽ‰, while the original image remains the same, the image served on client-side will become

      The avatar after transformation

Got an idea about Cloudinary? Great. So how do we install Cloudinary to work with Nuxt?

Install Cloudinary in a Nuxt application

To start using Cloudinary in Nuxt application, we install @nuxjs/cloudinary module using one of the following commands:

yarn add @nuxtjs/cloudinary

//or
npm i --save-dev @nuxtjs/cloudinary

Then add @nuxtjs/cloudinary as a module to modules array in nuxt.config.js

/* nuxt.config.js */
module.exports = {
    modules: [
        '@nuxtjs/cloudinary'
    ]
}

And set up the configurations using cloudinary key field for the plugin to work by passing your Cloudinary cloud name to cloudName

/* nuxt.config.js */
module.exports = {
    modules: [
        '@nuxtjs/cloudinary', {
            cloudName: '<your-cloud-name>'
        }
    ]
}

//OR
module.exports = {
    modules: [
        '@nuxtjs/cloudinary',
    ],
    cloudinary: {
        cloudName: '<your-cloud-name>'
    }
}

There are several configurations we can pass here, in which:

  • cloudName: the name of your cloud in Cloudinary.
  • secure - Boolean, indicates if we want to use HTTPS. By default itโ€™s true
  • privateCDN - Boolean. By default, itโ€™s false. It indicates if we want to use a custom CDN name.
  • cname and secureDistribution - this is custom domain settings for HTTP and HTTPs CDN.
  • useComponent - Boolean, by default, itโ€™s false. If this flag is true, the Nuxt engine will use a set of specific Vue components with Cloudinary instead if injecting $cloudinary instance.

Once completed, the Nuxt engine will inject $cloudinary instance to all components used in the application. And now, we can start organizing and manipulating our images to be responsive and optimized.

You give the filter, Cloudinary returns the image.

Assume we have an image as below:

An avatar of a cat

And we want to make a rounded square avatar with only the face, at 100x100 pixels from it.

We can use $cloudinary().url(publicId, options) with

  • publicId (the path to the original image stored in Cloudinary database), and
  • options as Object containing desired transformations:
/* pages/index.vue */
export default {
 computed: {
  avatar() {
   return this.$cloudinary().url(`examples/avatar.png`, {
        width: 100,
        height: 100,
        radius: 'max',
        crop: 'thumb'
     })
    }
 }
}

In which:

  • width and height : new width and height in which we would like the delivered image to be.
  • radius: the border-radius the image will have. max to make it round, or any number as the percentage of the roundness.
  • crop: how do we want to crop the original image to the new width and height. crop mode can be scale, fit, crop, thumb, fill, etc.

And in the <template> we map avatar to src of <img>

<!-- pages/index.vue -->
<div>
    <img :src="avatar" alt="cat avatar">
</div> 

The result will be:

The cat avatar in round shape and 100x100px

If you look closely at the delivery URL, the transformations applied are presented there

The generated URL in Element inspector

which w_ stands for width, h_ stands for height, r_ for radius and c_ for crop respectively. And absolutely no CSS needed!

In case we need another rectangle avatar in 150x210px and a little bit of round corner (10%), just change the options accordingly:

/* pages/index.vue */
export default {
 computed: {
  avatar() {
   return this.$cloudinary().url(`examples/avatar.png`, {
        width: 150,
        height: 210,
        radius: 10,
        crop: 'thumb'
     })
    }
 }
}

And tada ๐ŸŽ‰, our result now is:

The rectange cat avatar generated by Cloudinary

Awesome. But there are more. ๐Ÿ˜‰

You give the path, Cloudinary takes care of the format & quality.

As we saw in our above example, we passed the image's public id with extension .png as requesting for PNG format exclusively. Nevertheless, this is not a good practice. Different browsers have specifically optimized image formats, such as WebP for Chrome/Firefox, JPEG for Safari, etc. Serving the right format to the right browser requires a bit of coding typically.

A better solution is to simply pass fetchFormat: auto to the options parameter of this.$cloudinary().url(publicId, options) and remove the original extension from the publicId (if any), as seen below:

/* pages/index.vue */
export default {
 computed: {
  avatar() {
   return this.$cloudinary().url(`examples/avatar`, {
        width: 150,
        height: 210,
        radius: 10,
        crop: 'thumb',
        fetchFormat: 'auto',
     })
    }
 }
}

The result looks the same, and in the Developer Inspector > Network tab, we see the image is served as webp instead of png in Chrome.

Format webp for generated image in Chrome

And the same code, in Safari, generates image type as jp2

Format jp2 for the same image in Safari

If you wish to specify a format for any image, you can always pass the valid format as String to fetchFormat property and have the image delivered in the desired format.

Neat. What if your images are stored outside of Cloudinary?

Store images one place, and pipe through Cloudinary CDN

Indeed. You do not need to move your images data to Cloudinary storage. Instead, you can use fetchRemote(url, options) of this.$cloudinary() instance, which receives a HTTP/HTTPS image url and options of transformations to apply on that image. For example, we have the following image link:

https://www.ezoic.com/wp-content/uploads/2019/06/kitty-1080x675.jpg

Which points to the below image:

A random kitty image

And we can fetch and manipulate it as our avatar image with the following code:

/* pages/index.vue */
export default {
 computed: {
  avatar() {
   return this.$cloudinary().fetchRemote(
        'https://www.ezoic.com/wp-content/uploads/2019/06/kitty-1080x675.jpg',
        {
            width: 100,
            height: 100,
            radius: 'max',
            crop: 'thumb',
            fetchFormat: 'auto',
         }
        )
    }
 }
}

That's it. Our avatar now becomes

Fetch the kitty image into Cloudinary pipeline

No out-of-context work needed. Isn't it great? ๐Ÿ˜„

What about Cloudinary components? Are they better than using the $cloudinary() instance?

Use Cloudinary components

Instead of using the default mode with $cloudinary injected, we can also choose to use the ready made Vue components with Cloudinary inside, by enabling the flag useComponent in nuxt.config.js

/* nuxt.config.js */
module.exports = {
    cloudinary: {
        cloudName: '<your-cloud-name>',
        useComponent: true
    }
}

Then we can start using CldImage and CldVideo for image and video components, respectively.

To create an avatar from the image with public id - examples/avatar.png , we can use CldImage component directly in the <template> section and pass transformation as attributes, as shown below:

<template>
    <cld-image
        public-id="examples/avatar"
        width="100"
        height="100"
        crop="thumb"
        radius="max"
        fetchFormat="auto"
        quality="auto" 
    />
</tempate>

The result stays the same.

The cat avatar in round shape and 100x100px

Also, we can use CldTransformation - a supportive nested component for CldImage to organize transformation options into groups for a better readability, such as:

<template>
    <cld-image public-id="examples/avatar">
        <cld-transformation width="100" height="100" crop="thumb" />
        <cld-transformation radius="max" />
        <cld-transformation fetchFormat="auto" quality="auto" />
    </cld-image>
</tempate>

Some of the advatanges of using Cloudinary components are:

  • It provides smart component that requires less code and generates the delivery url itself.
  • It has client-side support, especially responsiveness according to the container's viewport and built-in accessibility support.

A11y support for visual impaired

We can enable accessibility support for visual impaired users, by using accessibility attribute, such as applying color-blind effect to a graph with similar colors:

<template>
    <cld-image
        public-id="articles/Nuxt/pie_chart.jpg"     
        accessibility="colorblind"
    />
</tempate>

And from the original graph

Pie chart without accessibility in color

We now get a version that is friendly to users with deuteranopia

Pie chart in color-blind mode for deuteranopia users

Other values can be

  • monochrome - change an image's colors to monotone colors
  • darkmode - change an image's overall colors to match dark theme
  • brightmode - change an image's overall colors to match light mode.

Make it responsive

There are cases where we want the image to respond to the size of its container (or viewport) dynamically, especially in a responsive application. Re-calculating and reset width and height according to the viewport changes can be expensive and causes reflow and repaint for browser. An alternative way is to pass responsive as an attribute to cld-image:

<template>
    <div class="container">
    <cld-image public-id="examples/avatar.png" responsive />
  </div>
</tempate>

And have the image resized itself accordingly:

How Cloudinary image auto-resizes

That's it. Now we have responsiveness enabled without a single code line needed. How awesome is it? ๐Ÿ˜‰

Resources

  1. More Cloudinary transformation options you can check out and play around.
  2. The @nuxtjs/cloudinary module documentation for more advanced setup and use cases.

Summary

Yes, I have been a fan of Cloudinary since the first time I heard about it. The idea of being to manipulate your images on run-time without a single code line needed, especially CSS code, is simply fantastic. You only get what you need for images on the client-side. No more, no less. Together with Nuxt and other Nuxt modules, it boosts up your Jamstack power where you need it. And you can make images โ€œworth a thousand wordsโ€ without spending too much time dealing with them ๐Ÿ˜Š.

Experiment yourself and let me know how it goes for you ๐Ÿ˜‰. Iโ€™d love to discuss all different use cases and how they fit your (Jam)stack.

๐Ÿ‘‰ If you'd like to catch up with me sometimes, follow me on Twitter | Facebook.

Like this post or find it useful? Hit the share button ๐Ÿ‘‡๐Ÿผ ๐Ÿ˜‰