Images optimization with Cloudinary in Nuxt apps
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.
Update Note - The article is updated to the match the new API of the latest version (1.0.0+) π.
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
ornpm i βsave-dev @nuxtjs/cloudinary
to install the module package in your Nuxt app. - Add
@nuxtjs/cloudinary
as a module ofmodules
innuxt.config.js
- Add Cloudinary configurations, such as
cloudName
tocloudinary
key field innuxt.config.js
- Start using
this.$cloudinary.image
(orthis.$cloudinary()
for any version < 1.0.0) 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:
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:
In which:cloud-name
is your Cloudinary's cloud name, which you can find in the Dashboard or the Settings page of Cloudinary app.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
andh_100
- 100x100 pixels in sizec_thumb
- crop as a thumbnail with auto face detectionr_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
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βstrue
privateCDN
- Boolean. By default, itβsfalse
. It indicates if we want to use a custom CDN name.cname
andsecureDistribution
- this is custom domain settings for HTTP and HTTPs CDN.useComponent
- Boolean, by default, itβsfalse
. If this flag istrue
, the Nuxt engine will use a set of specific Vue components with Cloudinary in addition to the$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:
And we want to make a rounded square avatar with only the face, at 100x100 pixels from it.
We can use $cloudinary.image.url(publicId, options)
(or $cloudinary().url(publicId, options)
for version < 1.0.0) with
publicId
(the path to the original image stored in Cloudinary database), andoptions
asObject
containing desired transformations:
/* pages/index.vue */
export default {
computed: {
avatar() {
return this.$cloudinary.image.url(`examples/avatar.png`, {
width: 100,
height: 100,
radius: 'max',
crop: 'thumb'
})
}
}
}
In which:
width
andheight
: 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 bescale
,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:
If you look closely at the delivery URL, the transformations applied are presented there
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.image.url(`examples/avatar.png`, {
width: 150,
height: 210,
radius: 10,
crop: 'thumb'
})
}
}
}
And tada π, our result now is:
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.image.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.image.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.
And the same code, in Safari, generates image type as jp2
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.image
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:
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.image.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
No out-of-context work needed. Isn't it great? π
What about Cloudinary components? Are they better than using the $cloudinary.image
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.
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
We now get a version that is friendly to users with deuteranopia
Other values can be
monochrome
- change an image's colors to monotone colorsdarkmode
- change an image's overall colors to match dark themebrightmode
- 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:
That's it. Now we have responsiveness enabled without a single code line needed. How awesome is it? π
Resources
- More Cloudinary transformation options you can check out and play around.
- 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 ππΌ π