As developers, we can all agree that page speed is important. The less time that your pages take to load, the more opportunities you have to get users to your page. Pinterest an American image sharing, and social media service designed to enable saving and discovery of information on the internet, drove user growth with performance improvements. To increase their load times, Pinterest looked at items “above the fold”, and only loaded those items to improve page speed performance. After doing this, they were able to speed up the main page by 40 percent. This led to a 15 percent increase in SEO, and a 15 percent increase in conversion to signup.
When considering most web page’s asset breakdowns, there are 4 major considerations: HTML, JavaScript, CSS and Images.
-
HTML: According to Web Almanac, HTML is typically the smallest resource that allows you to focus on the other three asset types.
-
JS/CSS: We will put hard limits on both of these items so that neither exceeds 5MB. If it does, we would start thinking of more ways to reduce this by lazy loading content, breaking that code up into chunks, or tree shaking comoponents.
-
Images: So, that huge Javascript payload you were worried about, that might be 2MB, is dwarfed by the first image that was loaded becuase it is 5MB. Since images comprise almost 75% of the total page weight, this is where we need to focus more of our time, especially if your images are above the fold.
Now that we know how important page load times is to our application, we can start to look at more modern approaches to loading images on the web. You would never want to load a 10MB image that someone took on there amazing new iPhone. You also wouldn’t want to load 12 versions of a given image just so that someone can pick the correct type.
-
Reduce size: You can do this on upload of your images take a 15,360 x 8,640 pixel image down to something more reasonable like 1,920 x 1,080 pixels to start.
-
Optimize: Ideally, you would generate several different sizes, and depending on either the device that is loading them, such as a mobile phone. Or, you can load each image based on the dimensions of the browser width that is being used.
-
Compression: AV1 Image File Format (AVIF) or WebP. These modern compressions can reduce image sizes between 25 to 50 percent without losing the amount of detail that is required on small devices, such as mobile phones. The great part about using something like AVIF or WebP is that you can also get lossless compression while still reducing size.
Please note that Next.js Image by default uses Sharp for image optimization
In order to apply the image optimizations from above, it can be a pretty daunting task. You would first need to decide on all of the different sizes that you want to create when you upload and image. Then you would also have to decide what image formats you will support. After deciding all of those things, you would write several server functions, and create storage locations backed by Content Delivery Networks.
Luckily, we live in the amazing time of Jamstack (JavaScript and Mark Up). We can use services like Cloudinary, Imgix, or Akamai to take care of all of this labor intensive work. That way, you don’t have to worry about losing any of your photos, and they will take care of all of those important transformations for you. This is often as simple as applying q_auto
as part of the url string so that a service like Cloudinary can apply the best image compression. You can also pass w_100
to apply a width when you only want to load a thumbnail sized image.
Now that you have a place to store and manipulate for your images, it is time to make using those services easier. This is where tools like next/image make a tremendous impact for Developer Experience (DX). The Image
component provided by next/image
is a wrapper for the HTML <img>
element.
A typical <img srcset
looks like the below. You can see that it specifies that up until a max width of 600px, it will use an image of 480px. But if its larger than that, it will use an image that is 800px. This allows us to use resolution switching for different sizes.
<img srcset="elva-fairy-480w.jpg 480w,
elva-fairy-800w.jpg 800w"sizes="(max-width: 600px) 480px,
800px"src="elva-fairy-800w.jpg"alt="Elva dressed as a fairy">
Code language: HTML, XML (xml)
This can quickly get very complicated for a developer to remember exactly how this specification works, and all of the different breakpoints that you might want to maintain. Below is an example of a production version of images on https://codingcat.dev.
That is a lot of code to remember and get correct. If any of those different links are off by one letter, it will fail to load that image on any given size. This means that you must also test all of the different sizes to check, and make sure your images are working correctly.
This is where next/image
shines as it simplifies this process, and makes it a lot easier to remember a simple syntax like below.
<Image
src="/ccd-cloudinary/nepnvay0yphf0dgg8ci6.png"
alt="amplify image"
width="480"
height="270"
layout="responsive"
/>
Code language: HTML, XML (xml)
Typically, you would need to provide a next/image
loader function in order to provide the correct url as seen in the example below.
const myLoader = ({ src, width, quality }) => {
return `https://example.com/${src}?w=${width}&q=${quality || 75}`
}
const MyImage = (props) => {
return (
<Image
loader={myLoader}
src="/me.png"
alt="Picture of the author"
width={500}
height={500}
/>
)
}
Code language: JavaScript (javascript)
Instead of writing this function repeatedly in your Next.js application, you can instead use several pre-built configurations for the cloud providers: Imgix, Cloudinary, and Akamai. In your next.config.js
file, this will look like below.
Imgix Example
module.exports = {
images: {
loader: 'imgix',
path: 'https://example.com/myaccount/',
},
}
Code language: JavaScript (javascript)
Cloudinary Example
module.exports = {
images: {
loader: 'cloudinary',
path: 'https://res.cloudinary.com/demo/',
},
}
Code language: JavaScript (javascript)