Optimizing Images with Next.JS & Vercel
Providing a simple demo of a Next.js site optimizing images that can be followed along. It also includes a deep dive into how these images are being optimized.
Post created: April 27, 2021
Post last updated: April 27, 2021
Did you hear!? Next.js version 10 now includes image optimization! All you have to do is switch an image tag, import one line, and add your height and width in that tag. Is it that easy? What's really happening here? These are all the questions I will answer while giving you an example template and showing you the results.
Let's Dive In and Clone a Next.js Project
If you want to follow along, just enter this in your terminal to clone the next.js starter I have for this image optimization example:
npx create-next-app nextjs-imgopt --use-npm --example "https://github.com/daletom/nextjs-imgblog"
Then go into the project
cd nextjs-imgopt
then you might need to install the dependencies npm install
or fix some outdated items npm audit fix
. Now you can run the site in development
npm run dev
and go to your localhost to see it in action.
Here is a completed example if you just want to skip to the end on Github or the live url on Vercel.
How Can I Tell These Images are Optimized?
Great question! I would suggest viewing this in Chrome or Firefox for the dev tools. I'm right clicking on one of the images and selecting inspect in my Chrome. The dev tools should open and you should see something like this:
This is showing a several srcsets
of an image, 320 wide, 420 wide, 768 wide, etc. This is a list of various sizes of each image that the browser can choose to load as the image. You will also notice several data-srcsets
as well. Those are generated because we are also lazy loading these images. The browser is actually using the sizes attribute to determine which size image it loads. You can be smart with sizes and declare various sizes depending on the size of your browser window which allows Chrome to select various srcsets for many different browser/device sizes viewing your images.
Another great way to tell if they are getting optimized? Look at the format of the image. If you are on a Chrome or Firefox browser, you will notice they are webp. Yep, these images are intelligently being formatted to webp!
Wondering which size of an image is being loaded? When you are inspecting, you can hover over a srcset and it will show a little preview of the image:
This shows the image is being viewed on the browser at a certain size (254 x 191 pixels) but the intrinsic value is the actual size of the image being loaded (yep, from srcset). This example is loading 420 x 315 pixels. I am fine with that, Chrome is being smart and increasing the size because I am on a MacBook Pro which has a screen with a high device pixel ratio (DPR). So Chrome is adding this larger option to ensure the image looks crisp on my screen.
These are all great examples of good image optimization practices. Adding various srcset
options for images, intelligent image formatting, declaring a sizes attribute to help with responsive design, lazy loading, and serving larger images to higher DPR devices. It sounds like a lot, but I actually barely did anything in the Next.js code to make this all happen!
How did the Image Optimization Get Added?
So we did a bunch of great image optimization items, but how did I do it? Instead of using an <img>
in your projects, you can drop in <Image>
instead. All you have to do is reference it with this line (I did this in my pages/index.js):
import Image from 'next/Image'
then replace the <img>
with <Image>
. In the examples for this project, I am using a local image in the public folder. I then added a large width and height that met the aspect ratio I needed. Why a large width and height? It generates additional srcsets
for the image, which is great because I am going to add a sizes attribute that matches the design of my site. I have added loading="lazy"
, which uses Chrome/Firefox's Native lazy loading. Then a low quality of 35. Don't worry, 35 isn't a % of 100, it's a lower setting that is fine for a blog. If you are doing high fashion, maybe make it a 70 :)
Lets see the example:
<Image
src="/images/terrarium.jpg"
alt="Terrarium"
width={1600}
height={1200}
loading="lazy"
quality={35}
sizes="(max-width: 600px) 100vw, (max-width: 1023px) 48vw, 23vw"
/>
What was that in the Sizes Attribute?
I know, I quickly skimmed over the sizes
attribute. But I was on a roll and I couldn't stop! What I'm doing is essentially telling the browser to find an image in the srcset
that closely meets these scenarios:
- if the browser is smaller than 600 pixels wide, find an image that is 100% the width of the browser
- if the browser is between 601 px and 1023 px wide, find an image that is 48% the width of the browser
- if the browser is larger than 1024 px, find an image that is 23% the width of the browser
This is an example of 4 columns in larger desktop, shifting to 2 columns, shifting to 1 single column on mobile. This is what the corresponding css looks like:
.column {
flex: 23%;
max-width: 23%;
padding: 0 4px;
}
@media screen and (max-width: 1023px) {
.column {
flex: 48%;
max-width: 48%;
}
}
@media screen and (max-width: 600px) {
.column {
flex: 100%;
max-width: 100%;
}
}
How much better are these Optimized Images?
So glad you asked. I actually included a link to a second page of unoptimized images that mirrors the 8 images and workflow we just did. Without optimizing those same images, they are 18.5 MB compared to 650 KB (depending on which size and type of browser you are on). That is an unbelievable size difference.
Deploy to Vercel & Done
I know everything has been pretty easy up to this point and this section is not any different. Use whatever means to get it on something like Github (I use Github desktop). Then import a new project in Vercel, choose this Github project, it will detect it is Next.js, and press deploy! Also, thanks for baring with me while I secretly just shared tons of fun photos of my kids with you all :) Again here is the final site you just made with me.