Optimize Images in a Next.js Using Custom Loaders

Banner for a MediaJam post

Emmanuel Ugwu

Images play a significant role in the development of the internet. It is a critical component of the web that improves a user’s experience, which can lead to an abysmal experience if unoptimized.

This article will utilize the Next/Image module shipped by Next.js to optimize rendered images. We’ll also add a ‘custom loader’ to improve the image optimization to add robust media transformations using Cloudinary.

Cloudinary offers a media management solution to manage, optimize, transform and deliver visual media.


This project is completed on CodeSandbox, and you can fork it to get started quickly.

You can also find the source code on GitHub.


To follow the steps in this article, you should have:

  • Adequate knowledge of JavaScript and React.js
  • The latest version of Node.js installed
  • A terminal such as ITerm2(MacOS) or Git bash (Windows)
  • A Cloudinary account - Create one here for free.

Setting up a Cloudinary account

After successfully creating an account, Cloudinary will redirect us to our account's dashboard, where we see account details that will be useful later on, including:

  • Cloud name
  • API Key
  • API Secret


NOTE: Do not share your Cloudindary credentials with anyone.

Installing the project dependencies

We create a Next.js app in a new folder called custom-loader-app by running the following command in our terminal:

1npx create-next-app custom-loader-app

Next, we’ll navigate into the project directory.

1cd custom-loader-app

We proceed to install the Cloudinary React SDK and Lodash.

1npm install cloudinary-react
2 npm install lodash

Running npm run dev starts a local development environment.


Adding a new local image with Next.js Image

Next.js provides the next/image module, a wrapper of the HTML Img element. Next/image bakes in various performance optimizations, improving the core web vitals of pages using it. Next/image supports both local and hosted images.

In our project's pages/components directory, we create a file called NextImage.js with the following content.

1import React from "react";
2 import Image from "next/image";
3 import styles from "/styles/Home.module.css";
4 const NextImage = () => {
5 return (
6 <div className={styles.imagecontainer}>
7 <Image
8 className={styles.image}
9 src="/small-dog.jpg"
10 alt="Food"
11 width={450}
12 height={280}
13 />
14 <h1 className={styles.nextcard}>Next Image</h1>
15 </div>
16 );
17 };
18 export default NextImage;

Here, we imported the next/image and rendered a local image. This image file with the title small-dog.jpg is in the project's public directory.

Next, we import and render the NextImage component into the home page on pages/index.js by replacing the existing content with:

1import Nextimage from "./components/NextImage";
2 import styles from "../styles/Home.module.css";
3 export default function Home() {
4 return (
5 <div className={styles.container}>
6 <h1>Image Optimization</h1>
7 <main className={styles.main}>
8 <Nextimage />
9 </main>
10 </div>
11 );
12 }

Adding an image using Cloudinary as a custom loader

Next/image allows the specification of a loader, which appends a domain URL to a local image, thereby serving the image from a content delivery network (CDN) and providing other optimization capabilities.

We require an Image component utilizing a loader. To do this, we create a file in pages/components/ titled CloudinaryImage with the following content:

1import React from "react";
2 import styles from "/styles/Home.module.css";
3 import Image from "next/image";
4 const CloudinaryImage = () => {
5 const cloudinaryImageLoader = ({ src }) => {
6 return `https://res.cloudinary.com/ugwutotheeshoes/image/upload/bo_10px_solid_rgb:f78585,e_blur:290,b_rgb:e1e6e9,c_scale,r_10,h_280,w_450/v1632752254/${src}`;
7 };
8 return (
9 <div className={styles.nextcard}>
10 <Image
11 loader={cloudinaryImageLoader}
12 src="eatery/item-8.jpg"
13 alt="Food"
14 width={450}
15 height={280}
16 />
17 <h1>Cloudinary Image</h1>
18 </div>
19 );
20 };
21 export default CloudinaryImage;

We specified a custom loader in the next/image Image component using Cloudinary. The loader defines a border, blur, crop, height, width, and radius applied as transformations.

Alternatively, we could specify the Cloudinary as a loader in next.config.js by doing updating the file to:

1module.exports = {
2 images: {
3 loader: 'cloudinary',
4 path: 'https://res.cloudinary.com/ugwutotheeshoes/image/upload/',
5 },
6 }

Lastly, we add the created CloudinaryImage component to our home page, updating the pages/index.js file to:

1import Nextimage from "./components/NextImage";
2import Cloudinary from "./components/CloudinaryImage";
3import styles from "../styles/Home.module.css";
4export default function Home() {
5 return (
6 <div className={styles.container}>
7 <h1>Image Optimization</h1>
8 <main className={styles.main}>
9 <Nextimage />
10 <Cloudinary />
11 </main>
12 </div>
13 );

The resulting image URL with a loader is: https://res.cloudinary.com/ugwutotheeshoes/image/upload/bo_10px_solid_rgb:f78585,e_blur:290,b_rgb:e1e6e9,c_scale,r_10,h_280,w_450/v1632752254/eatery/item-8.jpg

Here's what the home page should look like now, serving two images, one without a loader, and the other, with a loader.



In this post we discussed how to optimize images in Next.js apps using next/image. We also saw how to utilize custom loaders like Cloudinary to enhance the delivery image further.

You may be interested in reading these:

Emmanuel Ugwu

Software Engineering | Technical Writing | Web Development

I am a software engineer who is passionate about REST API's and building fast and scalable applications. I also write articles and other technical content to guide developers in the tech community.