In this article, we will build an image gallery with Next.js. We will be building this application with UI components from the KendoReact library and bringing in our images from Cloudinary.
In this post, we will be building an image gallery with Next.js and KendoReact. In a nutshell, we will build our Next.js application with components from the KendoReact library and bring in our image assets from Cloudinary.
This post assumes you are familiar with the basics of JavaScript and React.
To follow this article, I recommend you have the following:
If you don’t have a Cloudinary account, you can sign up for a free account. Log in after creating your account, and on your dashboard page, you should see all your credentials (cloud name, etc.).
We will be using Cloudinary’s list delivery type to generate a JSON listing of the images we will use in this project. Cloudinary allows us to list resources from the client-side based on their tags, so we need to add resource tags to the assets (images) we want to use in our gallery.
When you assign tags to assets, you can perform group actions on them. Cloudinary will generate a JSON snippet containing all the images with that specified tag. Information such as its format, type, dimensions, contextual metadata and structured metadata will be returned for each image. Follow the steps below to add tags to your images:
The URL syntax should look like this:
https://res.cloudinary.com/<your_cloud_name>/<resource_type>/list/<tag>.json
We will then query the URL to fetch a JSON list of all our images sharing the specified tag. Click here for more on adding tags to assets.
We also need to enable the image list delivery type because it is restricted by default. To enable it, click on the Security setting icon on your Cloudinary console. Click the Settings link on the security page and uncheck the Resource list option under Restricted media types.
Run the following commands to set up a Next.js project in a folder called image-gallery
:
npx create-next-app image-gallery
To navigate to your application directory and get the app running, run the following command:
cd kendo-cloudinary-gallery
npm run dev
This should start up your project on your browser at localhost:3000
.
KendoReact is distributed under a commercial license. The version I’m using (v4.10) supports using the KendoReact components without having or activating a license key just for development, but this fails during build. The steps to set up your license key are the same for paid members and those using the 30-day trial license.
Log in to your account and follow the steps here to download your license key. Copy the kendo-ui-license.txt
license key file you just downloaded to the root of your project.
Finally, we need to install the KendoReact license as a project dependency and activate it. Run the following command in your terminal to do that:
npm install @progress/kendo-licensing
npx install kendo-ui-license activate
KendoReact provides themes that we can use to style our application, and it currently ships five themes. Each theme includes a precompiled dist/all.css
CSS file that contains the styles for all KendoReact components. We will be using the default theme in this project, so run the following command in your terminal to install its package:
npm install --save @progress/kendo-theme-default
We also need to include the theme in our project by referencing the dist/all.css
in our App.js
file like so:
// Add this import before your existing App.css import
import "@progress/kendo-theme-default/dist/all.css";
While we need to include one of these themes, KendoReact UI components are designed to allow us to change the way your theme looks by updating a Sass or CSS file, and we can also add custom styles to a CSS file.
Let’s update Home.module.css
file with the following:
//styles/Home.modules.css
.container {
margin-top: 10rem;
min-height: 100vh;
padding: 0 0.5rem;
}
.grid {
margin-top: 3rem;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
}
@media (max-width: 600px) {
.grid {
width: 100%;
}
}
KendoReact is a library of over 100 UI components published as several npm packages scooped to @progress. We need to install different packages for the components we will be using in our project. Run the following command in your terminal:
npm install @progress/kendo-react-buttons @progress/kendo-react-common @progress/kendo-react-layout @progress/kendo-react-dialogs
Back to the pages directory, let’s update our index.js
file with the following:
// pages/index.js
import Head from "next/head";
import styles from "../styles/Home.module.css";
import CloudAssets from "../components/CloudAssets";
import { Typography } from "@progress/kendo-react-common";
export default function Home({ resData }) {
return (
<div className={styles.container}>
<Head>
<title>KendoUI Gallary </title>
<meta name="description" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className="wrap k-align-items-center">
<Typography.h2 textAlign={"center"} fontWeight={"bold"}>
Awesome Gallary
</Typography.h2>
<Typography.p
textAlign={"center"}
themeColor={"inverse"}
fontWeight={"light"}
>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Ducimus quam
eos consequatur, <br /> rem ratione nesciunt quisquam dolorem,
repudiandae officia totam amet corporis illum <br /> minus fugit
incidunt magnam deserunt veniam dignissimos.
</Typography.p>
<CloudAssets data={resData} />
</main>
</div>
);
}
export async function getStaticProps(context) {
const res = await fetch(
`https://res.cloudinary.com/ifeomaimoh/image/list/v1633911053/city.json`
);
const resData = await res.json();
if (!resData) {
return {
notFound: true,
};
}
return {
props: { resData },
};
}
In the code above, we’re also exporting getStaticProps()
in the same file as the HomePage
component, and it returns an object with props. We’re fetching our data in the getStaticProps()
function from the URL we got from Cloudinary after tagging our images, and the response is returned in the props
object, which will be passed to the Home
component as props.
You can also see how we’re using and styling different variants of the Typography
component included in the KendoReact Common package to display our content.
Run this command to start your development server and head over to http://localhost:3000/
in your browser.
npm run dev
Now let’s display the data we fetched from Cloudinary. Create a folder at the root of your project called components. Create a file called CloudAssets.js inside the folder and add the following to it:
components/CloudAssets.js
import styles from "../styles/Home.module.css";
import {
Card,
CardHeader,
CardImage,
CardTitle,
CardBody,
Avatar,
} from "@progress/kendo-react-layout";
const CloudAssets = ({ data }) => {
const baseUrl = "https://res.cloudinary.com/ifeomaimoh/image";
return (
<>
<div className={styles.grid}>
{data &&
data.resources.map((item) => {
const { format, public_id, version, type } = item;
return (
<div
style={{ padding: "10px" }}
key={version}
>
<Card>
<CardHeader className="k-hbox">
<Avatar type="icon" size="small" shape="circle">
<img
src="https://a.storyblok.com/f/51376/x/da286b5766/cloudinary.svg"
alt="avatar"
width="45px"
height="45px"
/>
</Avatar>
<CardTitle
style={{
marginBottom: "4px",
}}>
Somewhere in London
</CardTitle>
</CardHeader>
<CardBody>
<CardImage
src={`${baseUrl}/${type}/v${version}/${public_id}.${format}`}
alt="first from cloud.."
width="420px"
height="300px"
/>
</CardBody>
</Card>
</div>
);
})}
</div>
</>
);
};
export default CloudAssets;
In the code above, we’re also pulling out the data we’re yet to pass as props to this component—CloudAssets
. Remember the response returned in the props
object when we used the getStaticProps()
function to fetch our data from Cloudinary? That data is available as props and will be passed to this CloudAssets
component. We are mapping over the data and using the KendoReact Card
component and its contents, which are part of the KendoReact Layout package, to display our images.
We’re are also generating Cloudinary URLs for our images and passing them to the src
attribute of the CardImage
component.
Now, let’s import our CloudAssets
component to our index.js
file. Add the following to your index.js
file:
//pages/index.js
import CloudAssets from "../components/CloudAssets";
Now we can render the component CloudAssets
inside our Home
component. Add the following to your Home
component:
<main className="wrap k-align-items-center">
...
<Typography.p
textAlign={"center"}
themeColor={"inverse"}
fontWeight={"light"}
>
... incidunt magnam deserunt veniam dignissimos.
</Typography.p>
<CloudAssets data={resData} />
</main>;
If you check your browser, your application should look like this:
Our application already looks great, and this is only the tip of the iceberg compared to how much you can accomplish with Cloudinary and KendoReact.
That being said, let’s add another function to our app. Let’s make our images expand, showing a bigger image and a description whenever a user clicks on any of them.
To achieve that, let’s create another file inside the components
folder called Modal.js
and add the following to it:
//components/Modal.js
import { Button } from "@progress/kendo-react-buttons";
import { Dialog, DialogActionsBar } from "@progress/kendo-react-dialogs";
import { Typography } from "@progress/kendo-react-common";
import {
Card,
CardHeader,
CardTitle,
CardBody,
CardActions,
CardImage,
Avatar,
} from "@progress/kendo-react-layout";
function Modal({ baseUrl, data, setIsOpen }) {
const { format, public_id, version, type } = data;
const closeDialog = () => {
setIsOpen(false);
};
return (
<Dialog onClose={closeDialog} width={620} height={720}>
<Card>
<CardHeader
style={{
display: "flex",
justifyContent: "space-between",
}}
>
<Avatar type="icon" size="medium" shape="circle">
<img
src={`${baseUrl}/${type}/v${version}/${public_id}.${format}`}
alt="dialog avatar"
width="45px"
height="45px"
/>
</Avatar>
<CardTitle>Somewhere in London</CardTitle>
<CardActions>
<Button primary={true} look="outline" onClick={closeDialog}>
X
</Button>
</CardActions>
</CardHeader>
<CardBody>
<CardImage
src={`${baseUrl}/${type}/v${version}/${public_id}.${format}`}
alt="dialog image"
width="550"
height="450"
/>
</CardBody>
</Card>
<DialogActionsBar>
<Typography.h3 margin={"xlarge"} padding={5}>
Details: This Image is from{" "}
<span>
<a
href="https://res.cloudinary.com/kizmelvin"
target="_blank"
rel="noreferrer"
>
Cloudinary
</a>
</span>
</Typography.h3>
<Typography.h3 margin={"xlarge"}>
Credits:{" "}
<span>
<a href="https://unsplash.com/" target="_blank" rel="noreferrer">
Unsplash
</a>
</span>
</Typography.h3>
</DialogActionsBar>
</Dialog>
);
}
export default Modal;
We’re using the KendoReact Dialog
component in the code above, which is part of the KendoReact Dialogs package, to display a bigger size of our images and show additional information. It provides us a modal window, so whenever a user clicks on any image card, our Modal
component receives the properties of that image as props and displays it. We’re also using the properties of the image to generate the image URL.
The closeDialog()
function, as its name implies, is used to close the modal when a user clicks the close button.
Now let’s update our CloudAssets.js
file to render the modal we just created. Replace whatever is in your CloudAssets.js
file with the following:
//components/CloudAssets.js
import { useState } from "react";
import styles from "../styles/Home.module.css";
import Modal from "./Modal";
import {
Card,
CardHeader,
CardImage,
CardTitle,
CardBody,
Avatar,
} from "@progress/kendo-react-layout";
const CloudAssets = ({ data }) => {
const [isOpen, setIsOpen] = useState(false);
const [modalData, setModalData] = useState(null);
const baseUrl = "https://res.cloudinary.com/ifeomaimoh/image";
return (
<>
<div className={styles.grid}>
{data &&
data.resources.map((item) => {
const { format, public_id, version, type } = item;
return (
<div
style={{ padding: "10px" }}
key={version}
onClick={() => {
setIsOpen(true);
setModalData(item);
}}
>
<Card>
<CardHeader className="k-hbox">
<Avatar type="icon" size="small" shape="circle">
<img
src="https://a.storyblok.com/f/51376/x/da286b5766/cloudinary.svg"
alt="avatar"
width="45px"
height="45px"
/>
</Avatar>
<CardTitle
style={{
marginBottom: "4px",
}}
>
Somewhere in London
</CardTitle>
</CardHeader>
<CardBody>
<CardImage
src={`${baseUrl}/${type}/v${version}/${public_id}.${format}`}
alt="first from cloud.."
width="420px"
height="300px"
/>
</CardBody>
</Card>
</div>
);
})}
</div>
{isOpen && (
<Modal baseUrl={baseUrl} data={modalData} setIsOpen={setIsOpen} />
)}
</>
);
};
export default CloudAssets;
We imported the Modal
component and created two state variables, isOpen
and modalData
, using the useState()
hook to keep track of which image is clicked. Initially, the value of isOpen
is set to false. In the div
containing each image card, we added an onClick
handler to set the value of isOpen
to true using the setIsOpen
function when a user clicks on a card.
On click, we’re also calling the setModalData
function to set the value of modalData
to the properties of the image that was clicked. Then we’re conditionally rendering the Modal
component only when the value or isOpen
is equal to true
.
In this post, we were able to set up KendoReact and Cloudinary successfully and demonstrated how to use them to build an image gallery with Next.js. With Cloudinary, we saw how easy it is to get our media assets and use them in our application. We also saw how easy it is to integrate KendoReact’s components into our application without worrying about writing long lines of CSS code. You can check out the KendoReact documentation for a complete list of amazing components you can use to build your application.