Skip to content

Dynamic Blog Thumbnails with Netlify and Cloudinary

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.

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.

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.

This post requires the following:

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

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

npx create-next-app blog-thumbnail

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

cd blog-thumbnail
yarn add netlify-cli -g # installs Netlify CLI globally
Code language: PHP (php)

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:

yarn add cloudinary bootstrap file-saver

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:

[build]
    functions = "functions"
Code language: JavaScript (javascript)

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

netlify 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:

// functions/thumbnail.js
const Cloudinary = require("cloudinary").v2;
require("dotenv").config();
    
const handler = async (event) => {
  const { title, author } = JSON.parse(event.body);
    
  let today = new Date();
  let date =
    today.getFullYear() + "-" + (today.getMonth() + 1) + "-" + today.getDate();
    
  try {
    Cloudinary.config({
      cloud_name: process.env.CLOUD_NAME,
      api_key: process.env.API_KEY,
      api_secret: process.env.API_SECRET,
      secure: true
    });
    
    const imageTransform = Cloudinary.url("banners/banner.png", {
      transformation: [
         // base image resize
        {
          width: 720,
          height: 360,
          crop: "scale"
        },
        // Post Title overlay
        {
          overlay: {
            font_family: "Neucha",
            font_size: 40,
            font_weight: "bold",
            text: `${title}`
          },
          width: 400,
          height: 200,
          opacity: 100,
          gravity: "center",
          x: 35,
          color: "#00000098",
          crop: "fit"
        },
          // Post Author Overlay
        {
          overlay: {
            font_family: "Roboto",
            font_size: 14,
            font_weight: "bold",
            text: `${author}`,
            text_decoration: "underline"
          },
          width: 400,
          height: 200,
          opacity: 100,
          gravity: "south_west",
          x: 20,
          y: 18,
          color: "#fff",
          crop: "fit"
        },
         // Date of creation
        {
          overlay: {
            font_family: "Roboto",
            font_size: 14,
            font_weight: "bold",
            text: `created at:%250A${date}`,
            text_decoration: "underline"
          },
          width: 400,
          height: 200,
          opacity: 100,
          gravity: "south_east",
          x: 20,
          y: 10,
          color: "#fff",
          crop: "fit"
        }
      ]
    });
    
    return {
      statusCode: 200,
      body: JSON.stringify({ data: imageTransform })
    };
  } catch (error) {
    console.log(error);
  }
};
    
Code language: JavaScript (javascript)

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.

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:

import axios from "axios";
import { useState } from "react";
import { saveAs } from "file-saver";
import "bootstrap/dist/css/bootstrap.css";
    
export default function IndexPage() {
  const [url, setUrl] = useState("");
  const [download, setDownload] = useState(false);
    
  const handleSubmit = async (e) => {
    e.preventDefault();
    
    const userInput = {
      title: e.target.title.value,
      author: e.target.author.value
    };
    
    const body = JSON.stringify(userInput);
    const config = {
      headers: {
        "Content-Type": "application/json"
      }
    };
    
    try {
      if (body) {
        const response = await axios.post(
          "/.netlify/functions/thumbnail",
          body,
          config
        );
        const { data } = await response.data;
        setUrl(data);
        [e.target.name].value = "";
      }
    } catch (error) {
      console.error(error);
    }
  };
    
  const handleDownload = async (e) => {
    saveAs(url, "thumbnail");
    setDownload(true);
  };
  return (
    <div className="">
      <nav className="navbar navbar-expand-sm navbar-white bg-white">
        <div className="container align-contents-center p-2">
          <form
            name="thumbnail-info"
            className="row g-3"
            onSubmit={handleSubmit}
          >
    
            <div className="col-md-6">
              <label htmlFor="title" className="form-label">
                Post Title
              </label>
              <input
                name="title"
                id="title"
                className="form-control"
                required
              />
            </div>
    
            <div className="col-md-6">
              <label htmlFor="author" className="form-label">
                Author's Name
              </label>
              <input
                name="author"
                id="author"
                className="form-control"
                required
              />
            </div>
    
            <div className="col-12">
              <button type="submit" className="btn btn-primary btn-lg">
                Create thumbnail
              </button>
            </div>
          </form>
        </div>
      </nav>
    
      <div className="container">
        {url ? (
          <div>
            <img
              src={url}
              className="img-thumbnail align-contents-center"
              alt="transformed banner"
            />
            <div>
              <button
                onClick={handleDownload}
                className={download ? "btn btn-success" : "btn btn-primary"}
                disabled={download ? true : false}
              >
                {download ? "Downloaded" : "Download"}
              </button>
            </div>
          </div>
        ) : (
          ""
        )}
      </div>
    </div>
  );
}
Code language: JavaScript (javascript)

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

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.

  • 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
Back to top

Featured Post