Dynamic Blog Thumbnails with Netlify and Cloudinary

Banner for a MediaJam post

Ekene Eze

If you own a personal blog or maintain your company's blog, you will often find yourself creating OG images, thumbnails, and graphics for publishing every new blog post. This is very routine and very repetitive. Hence, it can be automated.

In this post, we'll show you how to automatically generate your blog post images and thumbnails from Cloudinary when you supply the post title and author to a Netlify function.

Technologies

Netlify Functions allow us to deploy server-side code that works as API endpoints, runs automatically in response to events, or processes more complex jobs in the background. Very recently, Netlify functions offering has grown with the addition of new features like functions scheduling (CRON) and Edge functions which we'll talk about in a later post.

Cloudinary allows us to transform images and videos to load faster with no visual degradation. With Cloudinary, you can automatically generate image and video variants, and deliver high-quality responsive experiences to increase conversions.

Sandbox

If you'd like to get a headstart by looking at the finished demo, I've got it set up here on Codesandbox for you!

We completed this project in a CodeSandbox. Fork and run it to quickly get started.

Prerequisites

This post requires the following:

  • Experience with JavaScript and React.js
  • Installation of Node.js
  • Next.js
  • A Cloudinary account. Signup

Setup and Installations

First, we will create a Next.js boilerplate with the following command:

1npx create-next-app blog-thumbnail

Next, we’ll navigate into the project directory and install netlify-cli with the following command:

1cd blog-thumbnail
2yarn add netlify-cli -g # installs Netlify CLI globally

Next, let’s also install the following yarn packages:

  • Cloudinary - So we can interact with the Cloudinary APIs.
  • Bootstrap – To handle our project styling.
  • File-saver - Help save the generated thumbnail.

The following command installs the above packages:

1yarn add cloudinary bootstrap file-saver

Generating thumbnails via Netlify function

Next, create a netlify.toml file in the root directory and add the snippet below to read form input and generate the post thumbnail. For a start, update the toml file with this snippet:

1[build]
2 functions = "functions"

The above snippet tells Netlify where our functions will be located. In the terminal, run the following command to start the dev server:

1netlify dev

Netlify will start a live dev server at http://localhost:8888 and we should see the project on that address in the browser.

Next, let's create a functions folder in the root directory with a thumbnail.js file and add the following snippet:

1// functions/thumbnail.js
2const Cloudinary = require("cloudinary").v2;
3require("dotenv").config();
4
5const handler = async (event) => {
6 const { title, author } = JSON.parse(event.body);
7
8 let today = new Date();
9 let date =
10 today.getFullYear() + "-" + (today.getMonth() + 1) + "-" + today.getDate();
11
12 try {
13 Cloudinary.config({
14 cloud_name: process.env.CLOUD_NAME,
15 api_key: process.env.API_KEY,
16 api_secret: process.env.API_SECRET,
17 secure: true
18 });
19
20 const imageTransform = Cloudinary.url("banners/banner.png", {
21 transformation: [
22 // base image resize
23 {
24 width: 720,
25 height: 360,
26 crop: "scale"
27 },
28 // Post Title overlay
29 {
30 overlay: {
31 font_family: "Neucha",
32 font_size: 40,
33 font_weight: "bold",
34 text: `${title}`
35 },
36 width: 400,
37 height: 200,
38 opacity: 100,
39 gravity: "center",
40 x: 35,
41 color: "#00000098",
42 crop: "fit"
43 },
44 // Post Author Overlay
45 {
46 overlay: {
47 font_family: "Roboto",
48 font_size: 14,
49 font_weight: "bold",
50 text: `${author}`,
51 text_decoration: "underline"
52 },
53 width: 400,
54 height: 200,
55 opacity: 100,
56 gravity: "south_west",
57 x: 20,
58 y: 18,
59 color: "#fff",
60 crop: "fit"
61 },
62 // Date of creation
63 {
64 overlay: {
65 font_family: "Roboto",
66 font_size: 14,
67 font_weight: "bold",
68 text: `created at:%250A${date}`,
69 text_decoration: "underline"
70 },
71 width: 400,
72 height: 200,
73 opacity: 100,
74 gravity: "south_east",
75 x: 20,
76 y: 10,
77 color: "#fff",
78 crop: "fit"
79 }
80 ]
81 });
82
83 return {
84 statusCode: 200,
85 body: JSON.stringify({ data: imageTransform })
86 };
87 } catch (error) {
88 console.log(error);
89 }
90};

In the snippet above, we:

  • Import the Cloudinary package, using the NodeJS require method.
  • Import the dotenv package to handle our environment variables.
  • Create a handler function that takes in an event argument.
  • Destructure the post author and post title from the event.body object, then JSON.parse it.
  • Configure our Cloudinary instance with credentials from the Cloudinary dashboard.
  • Initiate the Cloudinary URL function, which takes in an image path and an array of transformations to be carried out on the base image. In our case, we want to overlay our post title and post author on the base image.

After successfully performing these transformations, Cloudinary returns a URL of the transformed image, which we then store in the imageTransform variable.

Fetch transformed Image

Next, we need to fetch the returned URL of the transformed image and display it in the browser. To achieve this, lets update the pages/index.js file. The final updates to the file should look like the snippet below:

1import axios from "axios";
2import { useState } from "react";
3import { saveAs } from "file-saver";
4import "bootstrap/dist/css/bootstrap.css";
5
6export default function IndexPage() {
7 const [url, setUrl] = useState("");
8 const [download, setDownload] = useState(false);
9
10 const handleSubmit = async (e) => {
11 e.preventDefault();
12
13 const userInput = {
14 title: e.target.title.value,
15 author: e.target.author.value
16 };
17
18 const body = JSON.stringify(userInput);
19 const config = {
20 headers: {
21 "Content-Type": "application/json"
22 }
23 };
24
25 try {
26 if (body) {
27 const response = await axios.post(
28 "/.netlify/functions/thumbnail",
29 body,
30 config
31 );
32 const { data } = await response.data;
33 setUrl(data);
34 [e.target.name].value = "";
35 }
36 } catch (error) {
37 console.error(error);
38 }
39 };
40
41 const handleDownload = async (e) => {
42 saveAs(url, "thumbnail");
43 setDownload(true);
44 };
45 return (
46 <div className="">
47 <nav className="navbar navbar-expand-sm navbar-white bg-white">
48 <div className="container align-contents-center p-2">
49 <form
50 name="thumbnail-info"
51 className="row g-3"
52 onSubmit={handleSubmit}
53 >
54
55 <div className="col-md-6">
56 <label htmlFor="title" className="form-label">
57 Post Title
58 </label>
59 <input
60 name="title"
61 id="title"
62 className="form-control"
63 required
64 />
65 </div>
66
67 <div className="col-md-6">
68 <label htmlFor="author" className="form-label">
69 Author's Name
70 </label>
71 <input
72 name="author"
73 id="author"
74 className="form-control"
75 required
76 />
77 </div>
78
79 <div className="col-12">
80 <button type="submit" className="btn btn-primary btn-lg">
81 Create thumbnail
82 </button>
83 </div>
84 </form>
85 </div>
86 </nav>
87
88 <div className="container">
89 {url ? (
90 <div>
91 <img
92 src={url}
93 className="img-thumbnail align-contents-center"
94 alt="transformed banner"
95 />
96 <div>
97 <button
98 onClick={handleDownload}
99 className={download ? "btn btn-success" : "btn btn-primary"}
100 disabled={download ? true : false}
101 >
102 {download ? "Downloaded" : "Download"}
103 </button>
104 </div>
105 </div>
106 ) : (
107 ""
108 )}
109 </div>
110 </div>
111 );
112}

In the snippet above, we

  • Import the necessary packages
  • Create and export our page function.
  • Return a JSX form containing inputs for the post title, author and a submit button
  • Create a handleSubmit function to handle our form submission logic.
  • Pass the user data (post title & post author) in a POST request using Axios.
  • Use the useState hook to create a state variable called url to store our transformed image URL.
  • Check if the transformed image url exists, if it does, we render the image.
  • Render a download button to call our handleDownload function which in turn downloads the generated thumbnail to our device using the saveAs package.
  • Finally, we used a condition to ensure that the download button is only visible when our function returns URL data.

When we have the above completed, you should see the following on your browser

browser screen

Conclusion

Imagine being able to generate thumbnails for all your blog posts just by providing the title of your post and clicking a button. We've made that possible in this post as we've walked through the process of creating a Next.js application that does just that. Feel free to deploy this project on Netlify or any other deployment platforms of your choice and use it as you see fit.

Next steps

  • Replace the base image with your preferred image
  • Generate all your thumbnails with ease
  • Here are some links to learn more about Cloudinary and Netlify functions.
  • The base image used for this post can be found here

Ekene Eze

Director of DX at Plasmic

I work at Plasmic as the Director of Developer Experience and I love executing projects that help other software engineers. Passionate about sharing with the community, I often write, present workshops, create courses, and speak at conferences about web development concepts and best practices.