Media is an important part in creating a good UX for potential customers of your store, but when serving large unoptimized images, you can end up giving them a poor experience with long page loads.
We can instead use Cloudinary to automatically optimize our images and resize them on the fly with Cloudinary's programmable media API to deliver a more performant online store.
Instructor: [0:00] Our store's looking good. We have beautiful imagery to support our products, but our images aren't necessarily optimized right now. To start off, if we look at our images, we're loading them in at 1200 by 1200.
[0:12] Probably the biggest we ever use them is 322 by 322, which, if we look at two times would be 644 by 644. If we look at our network tab at the request for those images, we see that we're looking at, well, 170, 176, 115. It's a little over the place, but they're all over 100 KB.
[0:31] Where that's not necessarily too bad, but I think we can do better. It's important to make sure that we're serving our images as small as possible for a couple of reasons.
[0:41] First of all, we want to make sure that when we're delivering these images to people, we're being responsible for trying to avoid using more bandwidth than we actually need.
[0:50] Especially considering some of our customers might be on slow Internet or maybe not have a lot of data to begin with. On top of that, slower pages means less conversions, when dealing with e-commerce. Generally speaking, the more lean our page is, the greater chance that somebody might actually buy something.
[1:08] While my demo site only has maybe 10 products, this is going to be even more important as we start scaling up more and more products inside of our store.
[1:16] Now, the cool thing is because we're using Cloudinary, Cloudinary actually has image optimization built-in in a couple of different ways. First of all, we can optimize the image quality, where we can compress it to a point where the human eye won't be able to perceive the difference.
[1:32] We can also optimize image format, where Cloudinary can automatically determine the best format to send.
[1:38] For instance, if my image is originally a PNG, Cloudinary can determine for that particular browser that's requesting the image, whether a PNG is the best choice to send or maybe it's a WebP or an AVIF, really whichever Cloudinary determines is going to give the best results, it'll send that to the browser as long as the browser can support that format.
[1:59] Finally, we can optimize by simply resizing our images, where, with Cloudinary, we can pass in the width and the height that we want. That way, we can resize them on the fly and make sure that we're serving them only as big as we actually need and use them in our app.
[2:13] To do this, we're going to use the official Cloudinary URL-Gen package, where we're going to be able to take this package.
[2:19] We're going to be able to generate images with Cloudinary by passing in the image ID, which we already have available because of our UI extension that we installed earlier, where we can say, for this public ID, I want to automatically optimize.
[2:34] I want to resize it, and it'll give us that URL. To start, I want to install this dependency. I'm going to copy that package name. I'm going to run yarn add Cloudinary URL-Gen.
[2:44] Since we are looking at the category page, let's go ahead and start off with that. The first thing we want to do is import Cloudinary so we can use it. At the top of my category slug file, I'm going to go ahead and import my Cloudinary from URL-Gen.
[2:57] Where if we look back at the documentation, we want to now create a new instance of Cloudinary. I'm going to copy that instance creation. I'm going to paste it above my function or component definition. Also, I'm going to get rid of the comments because we don't need it.
[3:10] Then, I can update the Cloud name to my Cloud name. Where remember, we can find that Cloud name at the top of our Cloudinary dashboard, where I can copy that value and paste it in. Let's try this out quickly just to see how it works.
[3:24] I'm going to create a new constant called URL. I'm going to set that equal to cld.image. Where inside of that image, we want to pass the public ID, or if we're using the fetch remote method, we want to pass in the image URL. Because we already have the public IDs or the Cloudinary IDs for our images, we're going to go ahead and use that.
[3:44] To test that out, we need an actual image for a product. What I'm going to do is I'm going to say for my products. I'm going to grab the first product. I'm going to say for my image.publicid, because I know that it's going to be available at image.public_id, we're going to be able to grab that image public ID and pass it to Cloudinary.
[4:04] Then to turn that into a URL, I'll say, .toURL in all caps. Where now, let's console.log out that URL to see if it worked. We can see in the console that we do have that image link.
[4:18] If I open it up, we can see that it's that hoodie, which is that first product. Before we start optimizing by adding additional methods to this, let's transform all of our images to use this API. How about inside of our products map, I'm going to say that I want to create a new constant called imageURL, where I'm going to set that equal to cld.image.
[4:39] Where inside, I want to pass in the image public ID. That's going to be located at product.image.public_id, which again is the Cloudinary ID. Then we can say toURL, like we did above. I'm going to take that image URL, and replace the existing one that we're grabbing, where I can then use that and transform it after we test and make sure that it's working.
[5:03] Just as expected, all of our images are still loading perfectly. Let's get started with these optimizations, where the first thing we want to do is quality. Before this toURL statement, I'm going to add .quality. I'm going to say I want that to be auto.
[5:17] All the images should look the same still, but let's look in the network tab. While we don't see substantial differences, we do start to see some gains where we no longer have any images above 170 KB.
[5:29] We can do better. Now, let's update the format. Where currently, we see they're all JPEG. In addition to the quality, I'm going to also set the format to auto. We can see that they are substantially smaller, where some of them are half the size or more where they're being served in the AVIF format, because Chrome the browser I'm using, supports that modern format.
[5:50] Again, the nice thing is, if my browser doesn't support that AVIF, which is usually some of the older browsers, Cloudinary knows better and it'll send the best format for that browser. I think we can shave off a little bit more by making sure that we don't send images that are much larger than we actually need.
[6:09] Looking through these images, probably get as large as 447 in size, and we can double that for retina. 447 * 2 is 894. Let's round up to a healthy 900. Where I can say, I'm going to append resize and I'm going to pass in W_900,H_900. Which stands for width and height of 900.
[6:33] Then, I can also update the width and the height since I'm mainly specifying that to be those values of 900 for my width and my height of 900, to make sure that the browser isn't working too hard. When the page reloads, first of all, we can see that the intrinsic size is 900 by 900 and we're seeing yet again even lower image sizes.
[6:56] To show the comparison quick, I ran git-stash so that we can see what this looks like before our Cloudinary transformations. We can see that our sweatshirt here was 173, and we can see all of them are well above 100 kilobytes in size.
[7:10] Again, back to with Cloudinary, we see our size is beautifully a lot smaller. We have this in our category pages. Let's replicate this in our other pages. To start off, I'm going to abstract this instance of Cloudinary, that way we don't have to redefine it everywhere.
[7:27] Under my source directory, I'm going to create a new folder called lib. Inside, I'm going to create a new file called Cloudinary.js. Inside, I'm going to start off by first importing Cloudinary to that file, but then I'm going to head back and I'm going to grab that new instance of CLD and I'm going to paste it right in at the top.
[7:48] We want to have the same ability to do the exact same thing with cld.image that we were doing on category slug, but we want to do with a function where we don't have to have all that set up every single time. I'm going to export a new function and I'm going to call that build image.
[8:04] Where inside, I'm going to first return cld.image and I'm going to pass in the source as is as an argument. I want to make sure that my images are always automatically optimized and formatted. I'm going to copy those attributes. I'm going to add that on by default.
[8:22] We could probably add toURL in there by default, and we can probably add some options for resize inside that function, but I'm not going to over engineer this for now. We can probably be fine how this is, where because we're going to be able to return our cld.image as is, we'll still be able to chain it like we're doing here.
[8:40] I'm going to first replace the existing instance of CLD inside of category slug. I'm going to get rid of my import and I'm also going to get rid of this CLD instance itself. Then I'm going to go above and I'm going to import my build image from @lib/cloudinary.
[9:00] Just as a reminder, the reason we can use @lib, because if we look at jsconfig, we already have that defined as a mapped statement. We can use @lib and easily access that Cloudinary. Now, I'm going to take that build image. I'm going to scroll down, and instead of the cld.image, I'm going to use that build image.
[9:19] I'm also going to get rid of the quality and the format since we have those specified by default. All of our images are still looking great on the category page. Next, let's do the home page images. Between this banner and all the featured images, we're loading about 855 kilobytes.
[9:37] First in my index.js, I'm going to import my build image and starting off with my hero, because we know this is a Cloudinary URL. I can pass in right in my source build image where I'm going to wrap the herobackground.publicid, where I'm going to chain my toURL for my product images.
[9:56] I'm going to go back over to category slug and I'm going to simply copy that same image URL that I'm doing there. I'm going to paste it in and I'm going to replace with my image URL, the product.image.url. We can see that we've dropped that all the way down to 231 kilobytes. Finally, the product page itself.
[10:16] Let's replace this big image. One last time, I'm going to import my build image. Then I'm going to wrap that image with build image. I'm going to paste in my public ID instead of my URL. I'm going to say I want to take that toURL.
[10:31] On the product page, we took this particular image down from 119 KB to 41. In review, images are something that can make or break performance. We want to make sure that we're serving as optimized of images as we can.
[10:45] Because we're already using Cloudinary for our images, we can take advantage of additional features such as auto-optimization, auto-formatting, and even resizing on the fly.
[10:54] Which means we're going to serve smaller images, which helps boost performance, avoid wasting bandwidth, and ideally helping boost our conversion.