Skip to content

Optimize Your Visuals on a Composable Next.js E-commerce Website

In a composable website, functionality is broken down into independent components, allowing for better flexibility and scalability. This modular approach changes how websites are designed, developed, and maintained. By embracing composability, you build robust and adaptable systems that meet modern standards.

In this post, you’ll see how Cloudinary comfortably fits into the composable architecture by building a simple e-commerce store that serves and renders images from different sources using the Cloudinary fetch feature. Cloudinary takes care of fetching the image, applying optimizations on the fly, and delivering it.

To follow along, you should know TypeScript or JavaScript on a basic level and have a Cloudinary account (it’s free!). 

The completed app is in this GitHub Repository.

Run the command below to set up a Next.js app with any name you wish:

$ npx create-next-app@latest <APPLICATION-NAME>

Select the options below in the process of setting up the app, or modify as you see fit:

Install the Cloudinary package with the command below:

$ npm i cloudinary

Now that the app is set up, you can add some basic styles with the code below to make it more appealing. First, change the directory into the application by navigating to the page.module.css file and replace the boilerplate code with the code below:


// src/app/page.module.css

.main {
  display: flex;
  flex-direction: column;
  justify-content: space-evenly;
  width: 100vw;
  height: 100vh;
  align-items: center;
}
.product-area {
  display: flex;
  flex-direction: row;
  justify-content: space-evenly;
  width: 800px;
  flex-wrap: wrap;
  margin-top: 2rem;
}
.main > h1 {
  margin: 3rem 0;
}
.card {
  padding: 1rem;
  margin: 0.2rem;
  width: 350px;
  border-radius: var(--border-radius);
  background: rgba(var(--card-rgb), 0);
  border: 1px solid rgba(var(--card-border-rgb), 0.15);
}
.card span {
  display: inline-block;
  transition: transform 200ms;
}
.card h2 {
  font-weight: 600;
  margin-bottom: 0.7rem;
}
.card p {
  margin: 0;
  opacity: 0.6;
  font-size: 0.9rem;
  line-height: 1.5;
  max-width: 30ch;
  text-wrap: balance;
}
.img {
  width: 100%;
}Code language: JavaScript (javascript)

Next, in the page.tsx file, replace the boilerplate code with the code below. This code snippet is the app’s foundation. Later, you’ll add image cards that display the images served and optimized by Cloudinary fetch.


/// src/app/page.tsx

import styles from './page.module.css';

const Home: React.FC = () => {
  return (
    <div className={styles.main}>
      <h1>My Pet Store!</h1>
      <div className={styles['product-area']}>
      </div>
    </div>
  );
};
export default Home;Code language: JavaScript (javascript)

Now that all the basic setup is done, you need to add the environment variables that will enable remote access to Cloudinary. To do this, add a new file, .env.local, to your project’s root directory and paste the code below in there. This file will house all the Cloudinary app keys you need.

CLOUDINARY_CLOUD_NAME=<CLOUDINARY_CLOUD_NAME>Code language: HTML, XML (xml)

To get your cloud name, navigate to your Cloudinary developer dashboard,where you’ll see it under the Product Environment Credentials section.

After this setup, you can now work on the fetch and optimize functionality.

To fetch and optimize images, you will first need to set up a reusable image component that will house all the functionality for fetching and applying image transformations to individual images.

Add a new folder in the src directory and call it components. In the components folder, add a new file called image-card.tsx. In the image-card.tsx file, paste the code below; this code snippet defines a React component, ImageComponent, that takes in prop values and constructs the Cloudinary fetch URL using these props to render an image with specified transformations applied.


import React from 'react';
import styles from '../app/page.module.css';

const cloudinaryFetchUrl = (
  imageUrl: string,
  transformations: string
): string => {
  const cloudName: string | undefined = process.env.CLOUDINARY_CLOUD_NAME;
  const encodedImageUrl: string = encodeURIComponent(imageUrl);
  return `https://res.cloudinary.com/${cloudName}/image/fetch/${transformations}/${encodedImageUrl}`;
};

interface ImageComponentProps {
  src: string;
  alt: string;
  transformations: string;
}

const ImageComponent: React.FC<ImageComponentProps> = ({
  src,
  alt,
  transformations,
}) => {
  const imageUrl = cloudinaryFetchUrl(src, transformations);
  return (
    <img
      className={styles.img}
      src={imageUrl}
      alt={alt}
    />
  );
};
export default ImageComponent;
Code language: JavaScript (javascript)

In the function, two variables are declared in the cloudinaryFetchUrl function: the cloud_name variable, which retrieves the Cloudinary cloud name from environment variables, and encodedImageUrl, which encodes the image URL to make it safe.

Next, the ImageComponentProps interface defines the props that the ImageComponent functional component will accept: src, alt, and transformations. In the ImageComponent, the cloudinaryFetchUrl function is called with src and transformations to generate the Cloudinary image URL, and then the image is returned.

This component decouples the image source from the optimization process. The app doesn’t need to know where the image is hosted. Cloudinary’s fetch function leverages this existing source without re-hosting the image, applying optimizations, and delivering it. This means the original image remains unaltered. This separation of concerns allows for independent updates.

First, you will need to set up some test data. The test data will be an array of objects holding four images and the transformation for each image.

Note:

In a production application, this data can come from an image storage service or a content management system.

To do this, in the src folder, add a new folder called lib; in the lib folder, add a new file called data.ts. In the data.ts file, add the code that defines the images and their various transformations:


export const products: ProductInfo[] = [
  {
    name: 'Dog Lease',
    price: 100,
    imageUrl:
      'https://cloudinary-app.s3.amazonaws.com/pexels-blue-bird-7210748.jpg',
    transformation:
      'w_400,h_400,c_scale,q_100,e_auto_brightness,e_auto_color:80',
  },
  {
    name: 'Dog Food',
    price: 100,
    imageUrl:
      'https://cloudinary-app.s3.amazonaws.com/pexels-mart-production-8434641.jpg',
    transformation:
      'w_400,h_400,c_scale,q_100,e_auto_brightness,e_auto_color:80',
  },
  {
    name: 'Backpack Carrier',
    price: 200,
    imageUrl:
      'https://images.pexels.com/photos/8358908/pexels-photo-8358908.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2',
    transformation:
      'w_400,h_400,c_scale,q_100,e_auto_brightness,e_auto_color:80',
  },
  {
    name: 'Dog Toy',
    price: 300,
    imageUrl:
      'https://images.pexels.com/photos/20843208/pexels-photo-20843208/free-photo-of-dog-with-toy-on-a-brick-pavement.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2',
    transformation:
      'w_400,h_400,c_scale,q_100,e_auto_brightness,e_auto_color:80',
  },
];

export interface ProductInfo {
  name: string;
  price: number;
  imageUrl: string;
  transformation: string;
}Code language: JavaScript (javascript)

In the code above, an array named products contains four objects. Each object represents a single product and includes name, price, imageUrl, and transformation properties. The first two products have images hosted on Amazon AWS, while the last two use stock images hosted on images.pexels.com. All products conform to the ProductInfo interface, which defines the structure of each product object.

All the images use similar transformations. Here’s a detailed explanation of the transformation:

  • w_400. Sets the width of the image to 400 pixels.
  • h_400. Sets the height of the image to 400 pixels.
  • c_scale. Resizes the image to the specified width and height without cropping, but the aspect ratio may change.
  • q_100. Sets the image quality to 100%, which means no compression is applied. The image retains maximum quality.
  • e_auto_brightness. Automatically adjusts the image’s brightness to an optimal level.
  • e_auto_color:80. Automatically adjusts the image’s color, improving it to 80% of the optimal level. The value 80 fine-tunes the intensity of the color adjustment.

By specifying dimensions and scaling options such as w_400, h_400, and c_scale, Cloudinary ensures that the images are resized to the specified dimensions, which helps to prevent our images from being oversized or undersized images, improving both visual appeal and page loading times. 

Additionally, setting the image quality (q_100) guarantees that the fetched images retain maximum detail while efficiently managing file sizes, enhancing performance across different devices and network conditions. Also, the automatic enhancements e_auto_brightness and e_auto_color:80 provide automated adjustments for brightness and color balance.

Now, in the page.tsx file, you’ll loop over the array to display each transformed image like so:


import ImageComponent from '@/components/image-card';
import styles from './page.module.css';
import { ProductInfo, products } from '@/lib/data';

const Home: React.FC = () => {
  return (
    <div className={styles.main}>
      <h1>My Pet Store!</h1>
      <div className={styles['product-area']}>
        {products.map((product: ProductInfo) => (
          <div className={styles.card} key={product.name}>
            <h2>{product.name}</h2>
            <ImageComponent
              src={product.imageUrl}
              alt={product.name}
              transformations={product.transformation}
            />
            Price: <p>${product.price}</p>
          </div>
        ))}
      </div>
    </div>
  );
};

export default Home;Code language: JavaScript (javascript)

Now run the app and you should see that the transformations you specified earlier have been applied to each image in comparison to the original images.

In this blog post, we transformed and optimized images hosted on external sources in a Next.js application using Cloudinary’s fetch feature. Expand your app by adding more image transformations and optimizing images fetched from other platforms. Learn more about how Cloudinary can help get you started today.

If you found this blog post helpful and want to discuss it in more detail, join the Cloudinary Community forum and its associated Discord.

Back to top

Featured Post