How to build a product page with Commerce Layer, Cloudinary and Contentful

Published on November 22, 2024

How to build a product page with Commerce Layer, Cloudinary and Contentful

The Contentful Developer Hero Program is about recognizing, supporting, and empowering the most passionate and talented developers in our community. We want to spotlight and reward developers who are actively engaged with Contentful, creating a bridge for collaboration between developers and our team. You can learn more about the program here.

In this post, Developer Hero Ignacio demonstrates how to build a product details page using the powerful combination of Commerce Layer, Cloudinary and Contentful (naturally). Follow along with this tutorial to build a product page for a fetching beanie, and soon you'll have the makings of your very own composable commerce empire. Just in time for Black Friday!

Boost your ecommerce experience with Commerce Layer and Cloudinary

Commerce Layer is an API-first commerce engine, battery-charged with a full suite of APIs, webhooks, and dev tools, focusing on the transactional side of the ecommerce experience (e.g., cart and order management).

On the other hand, Cloudinary is an end-to-end image and video-management solution, offering comprehensive APIs and covering everything from image and video uploads, storage, manipulations, and optimizations to delivery, all being powered by a fast content delivery network (CDN).

This tutorial will guide you through using these platforms cohesively so each of them have a crucial and well-defined responsibility, enabling and facilitating the overall management experience:

  • Cloudinary: providing asset management and access to our product images.

  • Commerce Layer: Enabling the ecommerce capabilities of the website by managing our product SKUs.

  • Contentful: being the main data entry point and host of the rich content associated with our product, as well as serving as the connector with Commerce Layer.

We'll go through the steps of building a Product Details Page (PDP) which will be responsible for displaying all the relevant information of our product so a final user can make the decision of buying it or not.

All the code in this tutorial can be found in this repository.

Prerequisites

To follow along, you’ll need:

  • A Contentful account. Sign up if you do not have one.

  • A Commerce Layer account. Sign up if you do not have one.

  • A Cloudinary account. Sign up if you do not have one.

  • Intermediate knowledge of JavaScript.

  • Node.js version 20 or above.

You'll also benefit from knowing a bit of Next.js, even though the data-fetching principles used in this post can be applied to any framework.

Commerce Layer project configuration

In order to complete your initial setup for Commerce Layer, you can follow steps 1 and 2 of their onboarding tutorial here. We don't need to complete steps 3 and 4 since we won't be placing orders in this guide.

After having run the commercelayer seed command, the following resources will be created in your Commerce Layer organization:

Commerce Layer project configuration

This is not an introductory Commerce Layer course, so I will limit myself to providing a diagram of the minimum required entities we need to take into account for our demo to work. I do encourage you though to take a deep look into both the developer documentation and the data model if you're not that familiar with the terms we'll cover here.

The Commerce Layer model

The Commerce Layer model is quite a thing! And as you can see I've omitted a lot of resources the seeder script has created for us. The model being this modular is one of the virtues of Commerce Layer as it allows every resource to have a single and well-defined responsibility.

In order to understand it, we have to read it from top to bottom, that is, starting from SKU: An SKU has many prices, but each price belongs to a single price list, a price list relates with many markets, and so on.

You can browse all these resources in the Resources tab of the dashboard.

The Resources tab

Creating SKUs

Now that the minimal configuration is in place, we can start creating some SKUs by going into the Apps tab and clicking on the SKUs app. I created SKUs for black, green, red, and purple beanies using the same configuration you can see in the second image.

Creating SKUs

Adding prices

Now we'll look for the Price Lists app and open it. You can use the Price List that was created as part of the initial seeding process. I'll go and create a new one by clicking on the New button. Here, we'll give it a name and set a currency for our list.

Adding prices

After being created, we can click on the New Price button and add prices for our SKUs.

New price

After creating all the prices, you'll end up with something like this:

My USD Price List

Adding inventory

Last but not least, we'll go into the Products section of the Apps tab and look for the Inventory app so we add a stock quantity for each SKU.

You'll see that we have direct access to our Stock Locations. Let's get into the US Warehouse stock location that was created during the seeding process. When clicking on the + Stock Item button, we'll be prompted to select a SKU and the available quantity in this location.

Adding inventory

After having added a stock item for all of our SKUs, we'll end up with a list like this in the Stock Location screen:

All stock items

Great! Since Commerce Layer manages the transactional aspect of our ecommerce, and we won't get into the nitty-gritty of cart management and order placement, this configuration is sufficient. We now have to create the API credentials needed by Contentful so we can link both systems.

Getting API credentials

In order to create them, we have to go into the API Credentials tab, click on the New button and select the Integration type. Give it a meaningful name and choose the Admin role.

Getting API credentials

Copy and save the Client ID, Client secret, the Authorization Scopes and the Base endpoint values. We'll need them now that we're getting back into our beloved Contentful!

But before doing so (and do this now rather than later), we also have to create a new API Credentials of type Sales channel. These will be needed for our frontend app to fetch data from Commerce Layer.

It's important that these are different from the Integration credentials we created before, as well as being of type Sales channel, since we don't want the front end to have the minimal possibility of being able to operate with sensitive data. These credentials will only have read access and are for public use. Again, copy everything and store the values somewhere for now.

Product and variant content types: Setting up the Commerce Layer application in Contentful

Contentful will be responsible for having a Product entry which will contain Variant entries, being that this is a mirror of the SKUs that are part of the same product. This content type will also have a Rich Text field for the description of the SKU and an Attributes field, which is a multiref field for Attribute entries and has the label and value for the attributes of the variant.

Installing the Commerce Layer application

First, we need to install the Commerce Layer application in our environment. Click on the Apps tab present in the main Contentful dashboard, then on Marketplace. Then, click on the ecommerce tab, choose the Commerce Layer app, and finally, install and authorize access for the application in the desired environment.

Installing the Commerce Layer application

Now the Installation screen will show up. Enter the credentials values obtained from Commerce Layer in the last section and click Install.

Commerce Layer configuration

The Attribute content type

Now that our app was successfully installed, we can first create the Attribute content type. This will be responsible for defining the variant-defining attributes.

The Attribute content type

For our demo, I'll create four entries, one for each color.

Variant-defining attributes can grow really fast in more complex product models. For example, we could also have a Size attribute, leading to more possible combinations for our products. To keep it simple, we'll just work with Color in this guide.

Another important thing to note is that Commerce Layer also has the ability to add metadata to each SKU. We're not using that to store the attributes of each SKU in this example, since we want these to be localizable, but Contentful is the perfect fit for that purpose.

The Variant content type

Let's continue with the Variant content type. One of the most important fields is a short text field named “SKU” which will contain the direct relationship with the SKU obtained from Commerce Layer.

The Variant content type

It's important that we choose the Commerce Layer appearance when configuring the field:

Configuring the field

Now, let's add the validation to our Attributes field so we can only select Attribute entries.

Validation

The Product content type

Now we can create the Product content type, which will be kind of a “wrapper” for our variants/SKUs, so we have a single entry point when getting the product using the Contentful API.

The Product content type

The most important piece of this content type is the Variants field since it's a many-ref field for Variant entries.

Validation

Creating our entries

Now that we have our content model in place, we can start creating some entries getting the SKUs from Commerce Layer.

Let's start with the Variant entries. When creating one of these, you'll see that the appearance of the SKU field looks like this:

Creating our entries

When clicking on “Add SKU,” a new screen will show up with the SKUs we created in Commerce Layer.

Add SKU

You can choose any of them and associate it with the entry. Now the SKU literal string will be the value for our SKU field. You can also go directly into the page of this SKU in Commerce Layer by clicking on the external link icon.

You can repeat this process for all of the SKUs you want to create for a specific product. I'll do it thrice more so I have four SKUs to play with.

For each of the SKUs, I'll also add an Attribute entry defining the variant color. Here's an example of the entry for the green color:

Green color

Now, let's proceed and create a Product entry, which will contain our Variant entries.

Classic beanie

Note how I have added four variants to this product and that its slug is classic-beanie. By doing this, we have a single entry point which we can use to get all of the product’s data when visiting the Product Details Page on the website.

As far as Contentful is concerned, that's all we have to do. This is a good moment for you to also obtain your Contentful API Key (basically your space ID and CDA/CPA), since we'll need it later to complete the code integration. Go to Settings > API keys in Contentful and generate a new API key. Your space ID and CPA/CDA access token will be listed there. Save these for later.

Now let's get into Cloudinary!

Cloudinary

Cloudinary will serve as the storage for our product assets. At this point, you should already have created your Cloudinary account and have got access to your Cloud. When you land on your dashboard, you'll see something like this:

Cloudinary

Obtain API credentials

Before getting into exciting stuff, I'd want us to check the API creds off.

At the top of the Dashboard you'll find the Product Environment section. Here you can get your Cloud Name. This is all we'll need for our demo. Since we won't be interacting with write-access endpoints, we don't need to create an API Key.

Uploading images

Now click on the Assets tab (red highlighted section on the dashboard image) so we can start uploading some images.

In order to upload an image, you can click on the blue Upload button at the top-right corner of the Media Library screen.

Uploading images

After doing so, you'll be prompted to choose your asset so it gets uploaded into Cloudinary.

In order for our integration to work as expected, we need to add a tag to each of the images we upload with the respective SKU code (the one we defined in Commerce Layer). This will become handy later in order to get the images of our product automatically. You can do so by uncollapsing the Advanced section at the bottom of the Upload screen.

For example, I'll upload the Black Beanie image, adding the tag BLKB001, and defining the public ID (asset identifier) of the image as black-beanie.

Black beanie

You can repeat the same process for every image you want to upload for your product. In my case, I uploaded four beanie images, one for each color, adding the respective SKU as a tag. You'll have access to them in the code repository as well. 😉

Purple beanie red beanie

If you forgot to add the tag while uploading the image, that's okay! You can also add it by double clicking on the image, going into the Metadata tab, add the tag, then click on Save.

Metadata

Cloudinary has many (actually a ton!) of cool features to transform your assets. This is not a Cloudinary-centric tutorial, but I do encourage you to take a look at everything you can do with Cloudinary here!

FYI — I only had a real image for the black beanie, but by using the Cloudinary APIs, I removed the background and changed the dimensions of that image. I also created the rest of the images using these APIs and by changing contrast and colors, and you can see the result is pretty good!

Resource list restriction

Before moving on, we need to make sure we have unrestricted the Resource List image type.

Cloudinary has several image types which can be granularly restricted, which means that in order to get them using the Cloudinary API, we need to provide a signature.

For the sake of simplicity, we'll just make sure the restriction is disabled for our demo to work, otherwise we'll not be able to fetch the images without providing the signature. You have to evaluate whether you're fine with this or if your use case actually requires you to restrict these type of images.

Click on the Configure cog icon at the bottom of the Cloudinary UI's right sidebar, then click on the Security tab, and finally uncheck Resource list from the Restricted image types.

Resource list restriction

Finally, make sure to scroll all the way down and click the orange Save button so we actually save this change!

Building the React application

We now have everything we need to start building our React application, the third parties setup, as well as the credentials.

We'll use Next.js as the frontend framework to build our website. We can start by running the Next command:

npx create-next-app ecommerce-pdp

Secondary configuration, like TypeScript, linting, Prettier, alias paths, src folder, etc., is up to you, but for this guide, I'll use the App Router instead of the Pages Router. The data-fetching principles we'll use can also be replicated in the Pages Router by using methods like getServerSideProps. You can also apply the fetching logic we'll use in a normal React application built from scratch or using something like Vite.

After creating the project, you'll end up with something like this:

Building the React application

We can delete the src/app/page.jsx and src/app/page.module.css files, we won't need them. Let's also update the src/app/globals.css file with the following code. It's basically a simple normalizer.

Getting product data

We'll create a product/[slug]/page.jsx file within the app folder. In this file, we'll be able to access the slug of the page when we visit a route like http://localhost:3000/product/my-product-slug.

We can start with something really simple, just so we see the slug on the page. Try and visit the URL I used above, you'll see the my-product-slug text in a blank page.

Product content from Contentful

Now we need to start fetching the data from Contentful, that is our Product entry.

Start by creating a .env file adding the following variables:

CONTENTFUL_SPACE_ID=<your-space-id>
CONTENTFUL_TOKEN=<your-cda/cpa-token>

If you use the CDA token, make sure you publish any change you make to the Product and Variant entries.

Now run the following so we have the Contentful dependency installed:

npm install contentful

Then we can create a src/services/contentful.js file which will contain our Contentful client and the getProduct function to fetch the Product entry.

We can now update our page file by using this function to fetch the entry from Contentful and then we're able to see our Product data.

We're now able to see something like this when visiting the page with the slug of the created product:

Local host

Great! We got the data from Contentful, but we're not quite done yet. We now need to fetch the data from Commerce Layer so we can get the price and inventory of our SKUs.

Product data from Commerce Layer

We can get started by running the following install command:

npm install @commercelayer/js-auth @commercelayer/sdk

  • @commercelayer/js-auth: a library that wraps around the authentication API so we're able to get an access token.

  • @commercelayer/sdk: a library that allows us to easily interact with the Commerce Layer API.

Let's go ahead and add three new variables to our .env file:

COMMERCELAYER_CLIENT_ID=
COMMERCELAYER_CLIENT_SECRET=
COMMERCELAYER_ORGANIZATION_SLUG=
# You can get the scope from the API Credentials page
COMMERCELAYER_SCOPE="market:id:{id} stock_location:id:{id}"


These are the Sales Channel API credentials we created above at the end of the Commerce Layer Project Configuration section. Remember that the organization slug is part of the base endpoint (https://slug.commercelayer.io).

Now we can create a new services/commercelayer.js file with the functions we will need in order to interact with the Commerce Layer API.

In the getToken function, we use the authentication method from the @commercelayer/js-auth library passing the clientId, clientSecret, and the organization slug using the client_credentials authentication method (you can read more about them here) in order to obtain the authentication token and its expiry. With the help of this function, we'll get the token so we're able to store it in a cookie for future use.

In the getSkus function, we use the Commerce Layer client passing again the organization slug and our access token received by parameter. We then call the skus.list() method specifying code_in: skuCodes.join(',') as the filter. This basically means: Give me all the SKUs whose code matches with any of the provided in the list separated by comma (skuCodes.join(',')).

Since we need to resolve the token before we start making any requests, this has to happen before even trying to render any server component, hence, the logic must be handled in the src/middleware.js file, which, as per the Next.js documentation:

“The middleware.js|ts file is used to write Middleware and run code on the server before a request is completed. (...) Middleware executes before routes are rendered. It's particularly useful for implementing custom server-side logic like authentication, logging, or handling redirects.”

With this, we'll always be checking if the cookie is truthy. If not, we use our getToken function to hit the Commerce Layer API, then store the newly generated token in the ecommerce-token cookie, which will be available in RSCs.

Additionally, I've added an exported config object with some rules for the matcher property so our middleware only gets called with our web page requests (more information here).

With this in place, we're finally able to hit the commerce API and get our SKUs's data. Let's modify our src/app/product/[slug]/page.jsx file a little bit to make use of the getSkus function and also to render the information.

As of now, the UI will look something like this:

All beanies

Resolving product images with Cloudinary

We have the product and its variants (SKUs), we have its ecommerce data such as inventory and price. We're missing the most appealing and flashy piece, the images!

First, let's add the last environment variable to our .env file: CLOUDINARY_CLOUD_NAME, and set the value you obtained from the Cloudinary dashboard.

In order to get the images from Cloudinary, we can use the following special endpoint: https://res.cloudinary.com/{cloud-name}/image/list/${tag}.json.

By specifying the cloud name and a tag, we can get all the images that have this tag. This is exactly why we previously added the SKU code of every Commerce Layer SKU as a tag to our product images.

Let's create a new file src/services/cloudinary.js which will contain the following code:

The getImagesByTag function receives a tag and gets all the images that have been tagged with it. Then, we return an object with the tag and an array with the URLs of these images.

Now, we can update our PDP component so we get the images along with the ecommerce data:

If you use the Next.js Image component as I did, you'll also need to update the next.config.mjs file, so, we enable the Cloudinary domain to be used in the image src property:

With this, we're able to see all of the relevant data of our SKUs in the UI!

All beanies part deux

Rendering the Product Details Page (PDP)

So far, I've tried to keep the UI simple so we're able to see the important pieces: fetching the data and being able to use it to render something on the UI. Now, we'll add a nice touch of design so it looks more appealing. 😉 This is, of course, just to put the pieces together in a nicer way as an example of how you can build your own PDP.

Let's create a product-details-page.module.css file in a new src/components folder with the following:

Let's then create a new file product-details-page.jsx in the same folder. This component will be responsible for handling the state of our app so we can change the selected SKU using some inputs of type “radio” and see how the UI reacts to this.

Finally, let's update our src/app/product/[slug]/product.jsx file with this piece of code:

With all of this in place, we're now able to see a nicer product details page, allowing us to change between the different SKUs and see the information on the page change accordingly.

Beautiful beanies

Wrapping up

Well, that was a lot! We've integrated several headless solutions, creating a framework to build a complete ecommerce experience for products with color variants.

We left a lot of things out. We barely got into Commerce Layer and its complete model for ecommerce transactional operations, which also allows you to add items to a cart and place an order. 

Another example is Cloudinary, which has many plug-and-play UI widgets that connect with your cloud and use images based on the filter criteria you provide. 

They actually have a Product Gallery widget which might be a perfect fit for this scenario! I encourage you to take a look into the extensive list of features that all of these vendors provide so you can boost your solution even more.

Small reminder that you can find all the code (+ some small improvements) in this repository.

Thanks for reading!

Subscribe for updates

Build better digital experiences with Contentful updates direct to your inbox.

Ignacio Miranda Figueroa

Ignacio Miranda

Contentful Developer Hero

Ignacio is a Contentful Developer Hero! He's also an informatics and computing civil engineer. Ignacio likes videogames, CELTA and electro music, and he loves to learn about new technologies.

Related articles

Need help choosing between Tailwind vs. Bootstrap for your project’s CSS framework? This article explains why the choice is important and helps you decide. 
Guides

Tailwind vs. Bootstrap: Comparing CSS frameworks

August 15, 2024

You can compare an API call to mailing a letter, where the API endpoint acts as a mailbox or destination address.
Guides

What is an API call?

February 9, 2023

When deciding between Svelte vs. React for your project, you need to weigh up the performance and developer features of each. This guide will help you choose.
Guides

Svelte vs. React: Choosing the best for features and performance

March 9, 2023

Contentful Logo 2.5 Dark

Ready to start building?

Put everything you learned into action. Create and publish your content with Contentful — no credit card required.

Get started