NextJS and Cloudinary — App Router Integration

3 mins read by Varchasvi Pandey

NextJS with Cloudinary integration by Varchasvi Pandey

I have struggled enough therefore, I am writing this post most concisely to end your struggle! You already know what NextJS and Cloudinary are so I am not setting up any base here either. You know what you are here for, so let’s jump straight into it!

Setting up Cloudinary NPM package

I am using cloudinary v2.0.0 with NextJS 14 and TypeScript.

npm i cloudinary@2.0.0

Create a cloudinary config file anywhere in your project directory. This will be used to setup Cloudinary once and will be used to import everywhere else.

import { v2 as cloudinary } from "cloudinary";

cloudinary.config({
  cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
  api_key: process.env.CLOUDINARY_API_KEY,
  api_secret: process.env.CLOUDINARY_API_SECRET,
});

export { cloudinary };

Here we imported v2 as cloudinary, configured it and exported it as cloudinary. We have to make sure that going forward, we import cloudinary from here only.

Next step is to create a Cloudinary account and get the three important variables: Cloud Name, API Key and API Secret. You can easily find them once you have created the account. Add these keys to your .env file.

CLOUDINARY_CLOUD_NAME=*****
CLOUDINARY_API_KEY=********
CLOUDINARY_API_SECRET=*****

Create an API Route to upload images to Cloudinary

We will start by creating a helper function to upload files. It will return a promise to make it easier for us to use it. I have added a custom return type to it (It is based on usecase, you don’t have to).

import { cloudinary } from "@/cloudinary/config"; // your config path
import { NextRequest } from "next/server";

type UploadResponse = 
  { success: true; result?: UploadApiResponse } | 
  { success: false; error: UploadApiErrorResponse };

const uploadToCloudinary = (
  fileUri: string, fileName: string): Promise<UploadResponse> => {
  return new Promise((resolve, reject) => {
    cloudinary.uploader
      .upload(fileUri, {
        invalidate: true,
        resource_type: "auto",
        filename_override: fileName,
        folder: "product-images", // any sub-folder name in your cloud
        use_filename: true,
      })
      .then((result) => {
        resolve({ success: true, result });
      })
      .catch((error) => {
        reject({ success: false, error });
      });
  });
};

Now, let’s create a route handler to receive image file or a blob.

export async function POST(req: NextRequest) {
  // your auth check here if required
  return NextResponse.json({ message: "success!" });
}

Before we start writing the code for route handler, let’s first write the client-side code that will send the image file. We will be using fetch API for this. I am not showing you how to stage a file using an input in HTML here.

const uploadStagedFile = async (stagedFile: File | Blob) => {
  const form = new FormData();
  form.set("file", stagedFile);

  // here /api/upload is the route of my handler
  const res = await fetch("/api/upload", {
        method: "POST",
        body: form,
        headers: {
          // add token
          // content-type will be auto-handled and set to multipart/form-data
        },
   });

  const data = await res.json();

  // we will return the uploaded image URL from the API to the client
  console.log(data.imgUrl);
}

Tip: Wrap all the async code with try-catch-finally blocks. I am not doing this in this example just to keep limited nesting on the screen.

Till this point, your route should be accessible and should be returning the expected json response. Now let’s get back to our route handler and handle the file shared in the API call by the client.

export async function POST(req: NextRequest) {
  // your auth check here if required

  const formData = await req.formData();
  const file = formData.get("file") as File;

  const fileBuffer = await file.arrayBuffer();

  const mimeType = file.type;
  const encoding = "base64";
  const base64Data = Buffer.from(fileBuffer).toString("base64");

  // this will be used to upload the file
  const fileUri = "data:" + mimeType + ";" + encoding + "," + base64Data;

  const res = await uploadToCloudinary(fileUri, file.name);

  if (res.success && res.result) {
     return NextResponse.json({ 
        message: "success", imgUrl: res.result.secure_url 
     }); 
   } else return NextResponse.json({ message: "failure" });
}

Again, please do wrap this implementation with a try-catch block!

And with that, we are ready to upload files to Cloudinary and get back the image URL. Obviously, you get a lot of things in return, you can explore the API and interface for that. You can compress your image before uploading to Cloudinary if you like.

If you are coming from NodeJS background, you will start thinking about Multer. Multer is not required here! NextJS .formData method helps us retrieve the file easily.

Now please continue building that awesome project! This part is not going to stop you anymore.