Build a Gravatar Service in Next.js

Emma Alder

Identification on the world wide web is a critical component that we connect with through various social media platforms. Gravatar, a service meaning Globally Recognized Avatar built by WordPress, provides a universal image that identifies you across the web. This image is proper, especially when you comment on blog sites and interact online.

In this tutorial, we'll discuss how to build a Gravatar service in NextJS. We'll use Cloudinary for media storage, transformation, and delivery.

Sandbox

You can find the source code of the entire project on Codesandbox. Fork it to run the code.

1<CodeSandbox id="long-sunset-hnrpp" title="build a gravatar service in next.js" />

Prerequisites

We focused on building this project with Next.js, a React.js framework. However, experience in JavaScript and React is a necessity. To follow through the steps, we also need to do the following:

  • Set up an account on Cloudinary. It is free to signup.
  • Have NodeJS and npm installed on our computer. We use npm, a package manager, to install dependencies for our program. NPM is available when you install node.
  • Use a code editor of our choice.

Installation

To set up the starter project for development, install the boilerplate on our machine and run the command.

1npx create-next-app nextvatar && cd nextvatar

After the installation of nextvatar, we need to install the required dependencies.

1npm install cloudinary-react lodash

cloudinary-react, a Cloudinary plugin we use to transform and render stored images from Cloudinary into our React components. The Cloudinary upload widget requires Lodash.

Cloudinary Widget

To handle image uploads, we’ll utilize the Cloudinary upload widget. First, we add the Cloudinary widget’s JavaScript file from a CDN to the head of the page in the sections/Layout.js. We'll create a folder titled 'sections' and in it a file titled Layout.js with the following content.

<!— Can you please mention to create folder ‘sections’ and new file ‘Layout.js’ —>

sections/Layout.js

1import Head from "next/head";
2
3 const Layout = ({ children, pageMeta }) => {
4 return (
5 <>
6 <Head
7 <script
8 src="https://upload-widget.cloudinary.com/global/all.js"
9 type="text/javascript"
10 ></script>
11 </Head>
12 <div>
13 <main>{children}</main>
14 </div>
15 </>
16 );
17 };
18 export default Layout;

With the widget script installed, we create a widget instance on the home page.

pages/index.js

1import React, { useState } from "react";
2 import Layout from "../sections/Layout";
3 import styles from "../styles/Home.module.css";
4
5 export default function IndexPage() {
6 const [url, setUrl] = useState(null);
7
8 const widget = () => {
9 setUrl(null);
10
11 window.cloudinary
12 .createUploadWidget(
13 {
14 cloudName: "terieyenike",
15 uploadPreset: "avatar"
16 },
17 (error, result) => {
18 if (!error && result && result.event === "success") {
19 setUrl(result.info.url);
20 }
21 }
22 )
23 .open();
24 };
25
26 return (
27 <div className={styles.container}>
28 <Layout>
29 <div className={styles.main}>
30 <h1 className={styles.heading}>NEXTVATAR</h1>
31 <p className={styles.small}>An enhanced Gravatar</p>
32 {/* Rendered JSX goes in here */}
33 </div>
34 </Layout>
35 </div>
36 );
37 }

Here, we configured the upload widget using our cloud_name and an upload preset in a widget function. The image URL returned from the upload is assigned to a state variable, url.

The cloud name is a unique identifier for our Cloudinary account. You can retrieve it from our Cloudinary dashboard. You can find out more about an upload preset here.

Trigger an image upload

With a complete widget setup, we define the JSX markup to render a button that opens the widget. We also defined a text field to display the returned URL after a successful upload.

pages/index.js

1import React from "react";
2 {/* other imports go below here */}
3
4 export default function IndexPage() {
5 {/* states go here */}
6
7 const widget = () => {
8 {/* cloudinary widget code */}
9 };
10 return (
11 <div className={styles.container}>
12 <Layout>
13 <div className={styles.main}>
14 <h1 className={styles.heading}>NEXTVATAR</h1>
15 <p className={styles.small}>An enhanced Gravatar</p>
16 <button onClick={widget} className={styles.btn}>
17 Push a new photo
18 </button>
19 <p className={styles.pStyle}>
20 <span>URL:</span> {url}
21 </p>
22 </div>
23 </Layout>
24 </div>
25 );
26 }

Finally, we can import the following:

  • The file Logo.js in the components folder that contains the brand name, Nextvatar into the component sections/Header.js
  • Also, include the Footer.js and Header.js files into the Layout.js file.

<!— can you please mention to create folder ‘components’ and new file ‘Logos.js’ —> We create a new folder called 'components' with the file 'Logo.js' having the following content.

components/Logo.js <!— Fix to BOLD instead of italics —>

1import React from "react";
2 import Link from "next/link";
3 import styles from "../styles/Home.module.css";
4 const Logo = () => {
5 return (
6 <Link href="/">
7 <a className={styles.logo}>
8 <span className={styles.logo__name}>Nextvatar</span>
9 </a>
10 </Link>
11 );
12 };
13 export default Logo;
14
15**sections/Header.js**
16<!— Fix to BOLD instead of italics —>
17
18 import Logo from "../components/Logo";
19 import styles from "../styles/Home.module.css";
20 const Header = () => {
21 return (
22 <header>
23 <div className={styles.container}>
24 <nav className={styles.nav}>
25 <ul className={styles.nav__ul}>
26 <Logo />
27 </ul>
28 </nav>
29 </div>
30 </header>
31 );
32 };
33 export default Header;

sections/Footer.js <!— Fix to BOLD instead of italics —>

1import styles from "../styles/Home.module.css";
2 const Footer = () => {
3 return (
4 <div>
5 <footer className={styles.footer}>
6 <address className={styles.container}>
7 &copy; {new Date().getFullYear()} | Made with &hearts;, remote.{" "}
8 <span>Built with NextJS and Cloudinary</span>
9 </address>
10 </footer>
11 </div>
12 );
13 };
14 export default Footer;

sections/Layout.js <!— Fix to BOLD instead of italics —>

1import Head from "next/head";
2 import Footer from "./Footer";
3 import Header from "./Header";
4 import { useRouter } from "next/router";
5 const Layout = ({ children, pageMeta }) => {
6 // component definitions go in here
7 return (
8 <>
9 <Head>
10 <script
11 src="https://upload-widget.cloudinary.com/global/all.js"
12 type="text/javascript"
13 >
14 </Head>
15 <div>
16 <Header />
17 <main>{children}</main>
18 <Footer />
19 </div>
20 </>
21 );
22 };
23 export default Layout;

<!— Final Layout.js page wonky, when I copied code over from procedure it did not function.. here is the fixed version

— const Layout = ({ children, pageMeta }) => { // component definitions go in here return (

1<div>
2 <Head>
3 <script
4 src="https://upload-widget.cloudinary.com/global/all.js"
5 type="text/javascript"
6 >
7 </script>
8 </Head>
9 <div>
10 <Header />
11 <main>{children}</main>
12 <Footer />
13 </div>
14 </div>
15 );
16};

— COMMENT: WILLIAM - I had shortened the codeblock to remove the lines of code in the 'Head' tag. However, I just put it back. —>

Styling the interface

This project uses CSS modules and regular stylesheets with the naming convention, [name].module.css.

One advantage of using CSS modules is that it allows us to use CSS class names without worrying about name clashes. It outputs unique class names for our document.

styles/Home.module.css

1@import url("https://fonts.googleapis.com/css?family=Poppins:200,300,400,500,600,700,800,900&display=swap");
2
3 .main {
4 font-family: "Poppins", sans-serif;
5 display: flex;
6 align-items: center;
7 justify-content: center;
8 min-height: 100vh;
9 flex-direction: column;
10 }
11
12 .container {
13 max-width: 75rem;
14 width: 85%;
15 margin: 0 auto;
16 }
17
18 .btn {
19 display: block;
20 margin-top: 2em;
21 border: 0;
22 padding: 1em 2em;
23 background: rgba(37, 99, 235, 1);
24 color: #fff;
25 cursor: pointer;
26 font-weight: 700;
27 }
28
29 .nav {
30 padding: 1.5em 0;
31 }
32
33 .img {
34 max-width: 100%;
35 }
36
37 .nav__ul {
38 display: flex;
39 align-items: center;
40 justify-content: space-between;
41 width: 100%;
42 }
43
44 .heading {
45 font-size: 2rem;
46 font-weight: 700;
47 margin-bottom: 0.2em;
48 }
49
50 .pStyle {
51 margin-top: 2em;
52 width: 100%;
53 word-wrap: break-word;
54 }
55
56 .pStyle span {
57 display: block;
58 font-weight: 700;
59 }
60
61 .small {
62 font-size: 0.75rem;
63 color: #1a1a1a;
64 }
65
66 .logo {
67 display: flex;
68 align-items: center;
69 color: rgba(37, 99, 235, 1);
70 margin: 0;
71 padding: 0;
72 }
73
74 .logo__name {
75 white-space: nowrap;
76 font-size: 1.125rem;
77 line-height: 1.75rem;
78 font-weight: 700;
79 letter-spacing: -0.025em;
80 }
81
82 .section {
83 padding: 2em 0;
84 }
85
86 .footer {
87 width: 100%;
88 text-align: center;
89 padding-bottom: 1em;
90 z-index: 999;
91 font-size: 0.75rem;
92 }
93
94 .footer span {
95 display: block;
96 margin-top: 0.5em;
97 }
98
99 @media screen and (max-width: 768px) {
100 .pStyle span {
101 font-size: 1rem;
102 }
103 }

With the above stylesheet created, we can import it wherever we create a new component and use it to style.

One thing to note here is we can update the _app.js file with some global syles. The styles will apply to all pages and components in our application.

To avoid conflicts, we may only import the stylesheet in the pages/_app.js file.

With this, we can manage the image upload portion of our Gravatar service.

Summary

In this post, we managed the image upload portion of a Gravatar service. In part 2, we will describe how to apply modifications to the uploaded image, transforming it into an avatar.

Resources

You may find these useful:

Emma Alder

Technical Writer at Hackmamba.io

Technical writer at Hackmamba.io