The power of compelling images increases the likelihood that a potential customer will make a purchase. When interactive, images provide a more profound experience for the customer, making them almost as tangible as the product themselves. Using Sanity, Cloudinary and Netlify I will show you how to set up your own interactive product media gallery with minimal effort.
In this tutorial, we will build the product Gallery management feature for an e-commerce web application using the Cloudinary Product Widget.
Sandbox
The completed project is on CodeSandbox. Fork it and run with it.
Prerequisites
Knowledge of React, Next.js, Sanity CLI, Netlify edge functions and a Cloudinary account is required to get the most out of this article.
Getting Started
Create a Next.js project by running the command below in your terminal.
1npx create-next-app product-gallery
Next, add the project dependencies using the following command:
1npm install axios @sanity/client @sanity/image-url netlify-cli dotenv -g
Next, navigate into the project directory.
1cd product-gallery
Then, run the command below to start the application.
1ntl dev
This should start our Next.js application on port 8888
.
Note: You need to have the Netlify CLI installed to run
ntl
commands.
Reading data from Sanity
Our product data will be coming from a Sanity CMS project we’ve previously created. You can pull in data for products from wherever you want, a REST endpoint, your database, or from a JSON file. Whatever works best for you.
Let’s initialize a new Sanity instance for our project. Create a /lib/sanity.js
file and update it with the snippet below:
1// sanity.js2import sanityClient from "@sanity/client";3export const client = sanityClient({4 projectId: "[Project ID]",5 dataset: "production",6 useCdn: true,7});
Here, we setup a new sanity client by passing it our projectId
and dataset
. We have now established a connection with Sanity to read data.
Fetching Products from Sanity
We are going to use Netlify functions to interface with our Sanity instance and fetch our products.
To do that, let’s setup a simple Netlify function to fetch our data. Create a functions/product.js
file from the root directory and update it with this snippet:
1// src/functions/product.js23const sanityClient = require("@sanity/client");4require("dotenv").config();56exports.handler = async (event) => {7 const client = sanityClient({8 projectId: process.env.PROJECTID,9 dataset: "process.env.DATASET,10 useCdn: true,11 token: process.env.TOKEN,12 apiVersion: "2022-08-10"13 });14 try {15 const products = await client.fetch(16 `*[_type == "product" && "chocolate" in tags]`17 );18 const res = await products;19 return {20 statusCode: 200,21 body: JSON.stringify(res)22 };23 } catch (error) {24 console.log(error);25 }26};
Here, we used a GROQ query to fetch all products from Sanity with a chocolate
tag and send it back as a response. As a result, if you visited our project at localhost:8888/.netlify/functions/product
you should get a response containing all our products from Sanity.
Now that we are successfully fetching our product data from Sanity, it only makes sense that we display them on the homepage for users to see. To do that, let’s update our pages/index.js
file to call our Netlify function and display the response (our products):
1// index.js2import imageUrlBuilder from "@sanity/image-url";3const builder = imageUrlBuilder(client);4export default function IndexPage({ products }) {5 return (6 <div className={styles.container}>7 <main className={styles.main}>8 <div className={styles.grid}>9 {products.map((product) => {10 const { defaultProductVariant = {} } = product;11 const { images } = defaultProductVariant;12 return (13 <div key={product._id}>14 <a href={`/product/${product._id}`}>15 <img16 className={styles.card}17 src={builder.image(images[0]).width(300)}18 alt="images"19 />20 </a>21 <h3>{product.title}</h3>22 </div>23 );24 })}25 </div>26 </main>27 </div>28 );29}30export async function getStaticProps() {31 const products = await axios.get(32 "http://localhost:8888/.netlify/functions/products"33 );34 return {35 props: {36 products: products.data,37 },38 };39}
With that, if we reload the homepage on the browser, we should now see our products from Sanity rendered for the users to see.
Adding Dynamic Product Details Pages
Now that we have our products displayed, we want to implement dynamic pages so that when you click on a product, we open a product details page where you can see more information about the product.
We’ll follow the standard Next.js approach of using getStaticPaths to generate the dynamic paths and build the pages on demand with our data. We’ll use the Cloudinary Product Gallery widget to display more images of the products. Create a p``ages/p``roduct/[id].js
file and add the following snippet:
1// product/[id].js2import axios from "axios";3export default function product() {4 return (5 // Cloudinary product widget goes here6 )7}8export default product;9export async function getStaticPaths() {10 const products = await axios.get(11 "http://localhost:8888/.netlify/functions/products"12 );13 const paths = products.data && products.data.map((prod) => ({14 params: { id: prod._id }15 }));16 return {17 paths: paths,18 fallback: false19 };20}
Here, we export the getStaticPath``()
function to make requests to our Netlify function and generate all our products via the paths we’ve specified. Then, we loop through the products and assigned id
to params.id
to match the product ID.
Next, update the file and pass the paths we want to generate to the getStaticProps() function and pass the generated product to our homepage:
1// product/[id].js2// ...3export async function getStaticProps({ params }) {4 const products = await axios.get(5 "http://localhost:8888/.netlify/functions/products"6 );7 const product =8 products.data && products.data.filter((prod) => params.id === prod._id);9 return {10 props: {11 product,12 },13 };14}
With this, we can pass the generated product to our IndexPage()
component for rendering. However, we don’t just want to render it. We want to use the Cloudinary Product Gallery Widget to display all the different images of the product.
Setting up Cloudinary Product Gallery Widget
The Cloudinary gallery widget relies on the Client-side asset lists feature to retrieve the list of images (or videos) with specified tags. To ensure that this feature is available on your Cloudinary account, you must ensure that the Resource list option is enabled. By default, the list delivery type is restricted. To enable it, open the Security settings in your console and clear the Resource lis**t item under Restricted media types**.
Add Gallery Context
The functionality for the gallery widget is delivered via CDN and instantiated in the index.js
file. However, we need access to the instantiated object elsewhere in our application to update the Widget on successful upload.
To avoid prop drilling, we’ll use React Context to pass this object to any component where it may be required. Create a /context/GalleryContext.js
file in the root directory and set it up like so:
1import { createContext } from "react";2const GalleryContext = createContext(null);3export default GalleryContext;
With our context in place, we can instantiate the widget and update the context value.
Instantiate Gallery Widget
To instantiate the gallery widget we will use Next’s Custom Document
to update the <html>
tags used to render a Page.
To override the default Document
, create the file pages/_document.js
as shown below:
1import Document, { Html, Head, Main, NextScript } from "next/document";23class MyDocument extends Document {4 render() {5 return (6 <Html>7 <Head />8 <body>9 <script10 src="https://product-gallery.cloudinary.com/all.js"11 type="text/javascript"12 strategy="beforeInteractive"13 ></script>14 <Main />15 <NextScript />16 </body>17 </Html>18 );19 }20}2122export default MyDocument;
The snippet above is the default Document
added by Next.js that allows custom attributes as props. We’re using it to embed our JavaScript file which incorporates all the product gallery functionality.
Next, update your products/[id].js
file to match the following snippet:
1// product/[id].js2// ...34const product = ({ product }) => {5 useEffect(() => {6 const productGallery = cloudinary.galleryWidget(7 {8 container: "#gallery",9 cloudName: "cloudName",10 mediaAssets: [{ tag: `${product[0].tags[0]}`, mediaType: "image" }],11 },12 []13 );14 productGallery.render();15 });16 return (17 <div className={styles.wrapper}>18 <div>19 <div id="gallery"></div>20 </div>21 <div>22 <div>23 <p className={styles.product}>{product[0].title}</p>24 <p>{product[0].description}</p>25 </div>26 <p className={styles.price}>$ 20 </p>27 <button className={styles.btn}>Add to Cart</button>28 </div>29 </div>30 );31};32export default product;3334// ...
Here, we initialize the Product Gallery Widget with the cloudinary.galleryWidge``t()
method call and pass the initialization options, including the cloud name and the tags of the media assets we wish to retrieve. We also specify a key named container
to be the div
element where the gallery will be rendered.
With these, we can now view the individual product and its variants. The final result will look like the GIF shown below.
Conclusion
In this tutorial, we went over the process of reading e-commerce product data from Sanity using Netlify functions and rendering multiple product variant images in a product display page using the Cloudinary Product Gallery Widget. We rendered only images in this project, however, the widget can also render videos, 360 spin sets and 3D models.
Resources you may find helpful: