{"id":27981,"date":"2023-08-07T07:00:00","date_gmt":"2023-08-07T14:00:00","guid":{"rendered":"http:\/\/upload-images-with-vercel-serverless-functions"},"modified":"2025-06-26T11:00:46","modified_gmt":"2025-06-26T18:00:46","slug":"upload-images-with-vercel-serverless-functions","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/upload-images-with-vercel-serverless-functions","title":{"rendered":"Upload Images With Vercel Serverless Functions"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><p>With serverless functions, we can write backend or server-side code without the burden of managing servers. One of the core features of Next.js is the <a href=\"https:\/\/nextjs.org\/docs\/api-routes\/introduction\">API routes<\/a> which provide an easy solution to create an API endpoint by allowing us to write server-side logic within our <a href=\"https:\/\/nextjs.org\/\">Next.js<\/a> application, which can then be deployed as <a href=\"https:\/\/vercel.com\/docs\/concepts\/functions\/serverless-functions\">Serverless Functions<\/a> to <a href=\"https:\/\/vercel.com\/docs?redirected=1\">Vercel<\/a>.<\/p>\n<p>In this article, we\u2019ll look at the process of uploading images from a Next.js application to <a href=\"https:\/\/cloudinary.com\/\">Cloudinary<\/a> using Vercel\u2019s Serverless Functions.<\/p>\n<\/div>\n\n<div class=\"wp-block-cloudinary-markdown \"><h2>Setting Up the Project<\/h2>\n<p>Create a <a href=\"https:\/\/nextjs.org\/\">Next.js<\/a> app using the following command:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">npx create-next-app vercel-severless-upload\n<\/code><\/span><\/pre>\n<p>Next, add the project dependencies using the following command:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">npm install cloudinary axios multer streamifier dotenv\n<\/code><\/span><\/pre>\n<p>The <a href=\"https:\/\/cloudinary.com\/documentation\/node_integration\">Node Cloudinary SDK<\/a> will provide easy-to-use methods to interact with the Cloudinary APIs. <a href=\"https:\/\/github.com\/axios\/axios\">axios<\/a> will serve as our HTTP client. <a href=\"https:\/\/www.npmjs.com\/package\/multer\">Multer<\/a> is a Node.js middleware used to handle multipart\/form-data, the dotenv module will allow us to parse environment variables defined in a .env file, and <a href=\"https:\/\/www.npmjs.com\/package\/streamifier\">streamifier<\/a> is used to convert a Buffer\/String into a readable stream.<\/p>\n<p>\nThere\u2019s an alternative way to handle image uploads by creating a Serverless Function to return a presigned URL, which wasn\u2019t covered in the initial setup. This method enhances security by generating a temporary URL for image uploads. Here\u2019s a simplified outline of the process:\n<\/p>\n<ol>\n<li>Create a Serverless Function to return a presigned URL.\n<li>Call this Serverless Function from the front-end to obtain the presigned POST URL.\n<li>Allow the user to select an image for upload on the front-end.\n<li>Forward the file to the obtained POST URL for upload.\n<\/li>\n<\/ol>\n<p>\nThis presigned URL mechanism is beneficial, especially when considering Vercel\u2019s serverless function limit of 4.5MB, as it advises against uploading large images directly to your file system. \n<\/p>\n<h2>Setting Up Tailwind<\/h2>\n<p>We\u2019ll use Tailwind CSS to give our application a decent look. Run the following command in your terminal:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">npm install -D tailwindcss postcss autoprefixer\nnpx tailwindcss init -p\n<\/code><\/span><\/pre>\n<p>Now, add the following to your <code>tailwind.config.js<\/code> file:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-built_in\">module<\/span>.exports = {\n  <span class=\"hljs-attr\">content<\/span>: &#91;\n    <span class=\"hljs-string\">\".\/pages\/**\/*.{js,ts,jsx,tsx}\"<\/span>,\n    <span class=\"hljs-string\">\".\/components\/**\/*.{js,ts,jsx,tsx}\"<\/span>,\n  ],\n  <span class=\"hljs-attr\">theme<\/span>: {\n    <span class=\"hljs-attr\">extend<\/span>: {},\n  },\n  <span class=\"hljs-attr\">plugins<\/span>: &#91;],\n};\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Add the following to your <code>styles\/global.css<\/code> file:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-keyword\">@tailwind<\/span> base;\n<span class=\"hljs-keyword\">@tailwind<\/span> components;\n<span class=\"hljs-keyword\">@tailwind<\/span> utilities;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h2>Setting Up Cloudinary<\/h2>\n<p>First, sign up for a <a href=\"https:\/\/cloudinary.com\/users\/register\/free\">free Cloudinary account<\/a> if you don\u2019t have one already. Displayed on your account\u2019s <strong>Management Console<\/strong> (aka Dashboard) are your Cloudinary credentials: your cloud name, API key, etc.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/media_jams\/s_6399234270E32E86954C834839875C7D66DECC144A068FA9F3D11325803EA9A5_1652792483315_CleanShot+2022-05-17+at+17.00.152x.png\" alt=\"\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"2000\" height=\"743\"\/><\/p>\n<p>Next, let\u2019s create environment variables to hold the details of our Cloudinary account. Create a new file called <code>.env<\/code> at the root of your project and add the following to it:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">    CLOUD_NAME = YOUR CLOUD NAME HERE\n    API_KEY = YOUR API API KEY\n    API_SECRET = YOUR API API SECRET\n<\/code><\/span><\/pre>\n<p>This will be used as a default when the project is set up on another system. To update your local environment, create a copy of the <code>.env<\/code> file using the following command:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-tag\">cp<\/span> <span class=\"hljs-selector-class\">.env<\/span> <span class=\"hljs-selector-class\">.env<\/span><span class=\"hljs-selector-class\">.local<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>By default, this local file resides in the <code>.gitignore<\/code> folder, mitigating the security risk of inadvertently exposing secret credentials to the public. You can update the <code>.env.local<\/code> file with your Cloudinary credentials.<\/p>\n<h2>Writing the Upload Logic<\/h2>\n<p>Let\u2019s create a Serverless Function that will be used to upload images to Cloudinary. Create a file called <code>upload.js<\/code> in your <code>pages\/api<\/code> directory. Next.js treats files created inside the <code>pages\/api<\/code> folder as API routes, and with these API routes, we can write some server-side logic to handle HTTP requests. In our case, we\u2019ll need just one route. Add the following to your <code>upload.js<\/code> file:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> multer <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"multer\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { v2 <span class=\"hljs-keyword\">as<\/span> cloudinary } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"cloudinary\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> dotenv <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"dotenv\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> streamifier <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"streamifier\"<\/span>;\ndotenv.config();\n<span class=\"hljs-keyword\">const<\/span> storage = multer.memoryStorage();\n<span class=\"hljs-keyword\">const<\/span> upload = multer({ storage });\n<span class=\"hljs-keyword\">const<\/span> uploadMiddleware = upload.single(<span class=\"hljs-string\">\"file\"<\/span>);\ncloudinary.config({\n  <span class=\"hljs-attr\">cloud_name<\/span>: process.env.CLOUD_NAME,\n  <span class=\"hljs-attr\">api_key<\/span>: process.env.API_KEY,\n  <span class=\"hljs-attr\">api_secret<\/span>: process.env.API_SECRET,\n  <span class=\"hljs-attr\">secure<\/span>: <span class=\"hljs-literal\">true<\/span>,\n});\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">runMiddleware<\/span>(<span class=\"hljs-params\">req, res, fn<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Promise<\/span>(<span class=\"hljs-function\">(<span class=\"hljs-params\">resolve, reject<\/span>) =&gt;<\/span> {\n    fn(req, res, (result) =&gt; {\n      <span class=\"hljs-keyword\">if<\/span> (result <span class=\"hljs-keyword\">instanceof<\/span> <span class=\"hljs-built_in\">Error<\/span>) {\n        <span class=\"hljs-keyword\">return<\/span> reject(result);\n      }\n      <span class=\"hljs-keyword\">return<\/span> resolve(result);\n    });\n  });\n}\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">handler<\/span>(<span class=\"hljs-params\">req, res<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">await<\/span> runMiddleware(req, res, uploadMiddleware);\n  <span class=\"hljs-built_in\">console<\/span>.log(req.file.buffer);\n  <span class=\"hljs-keyword\">const<\/span> stream = <span class=\"hljs-keyword\">await<\/span> cloudinary.uploader.upload_stream(\n    {\n      <span class=\"hljs-attr\">folder<\/span>: <span class=\"hljs-string\">\"demo\"<\/span>,\n    },\n    (error, result) =&gt; {\n      <span class=\"hljs-keyword\">if<\/span> (error) <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-built_in\">console<\/span>.error(error);\n      res.status(<span class=\"hljs-number\">200<\/span>).json(result);\n    }\n  );\n  streamifier.createReadStream(req.file.buffer).pipe(stream);\n}\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> config = {\n  <span class=\"hljs-attr\">api<\/span>: {\n    <span class=\"hljs-attr\">bodyParser<\/span>: <span class=\"hljs-literal\">false<\/span>,\n  },\n};\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>In the code above, we\u2019ll first bring the necessary imports. We\u2019ll set up the Multer middleware, which provides us with two storage options: disk and memory storage. To keep things simple, we\u2019ll create an instance of Multer and initialize it with the storage option because we want our parsed files to be stored temporarily on the RAM; its return values are stored in a variable called <code>upload<\/code>. Our application supports selecting one file, so we\u2019ll use Multer\u2019s <code>upload.single<\/code> method and pass a string that will later be used to check for and parse files in the request body.<\/p>\n<p>Next, we\u2019ll configure it with an object consisting of our Cloudinary credentials. We\u2019ll then define a utility function \u2014 <code>runMiddleware<\/code>, and as its name suggests, it will allow us to run our middleware. The function will wait for the Multer middleware to execute before continuing or throw an error if an error occurs.<\/p>\n<p>Finally, we\u2019ll define and export a request handler function that will be triggered by an HTTP request made to <code>\/api\/upload<\/code>. This function starts by parsing the file contained in the request body using the Multer middleware. Once the file is parsed successfully, the Multer middleware will attach a file property to the request body, i.e., <code>req.file<\/code>, which contains information about the file.<\/p>\n<p>Next, we\u2019ll pipe from a readable stream by converting the buffer in the parsed file to a readable stream using the <a href=\"https:\/\/www.npmjs.com\/package\/streamifier\"><strong>streamifier<\/strong><\/a> library.<\/p>\n<p>We\u2019ll pipe to the Cloudinary uploader using the <code>upload_stream()<\/code> method, which accepts two arguments. The first is an object where you can define your upload parameters (we define our destination folder \u2014 <code>demo<\/code>), and the second is a callback function that gets called when the piping is complete. The callback accepts two parameters \u2014 an error and the result. If an error occurs, it will be logged to the terminal, and if successful, we\u2019ll send the upload response we get from Cloudinary back to the client side.<\/p>\n<p>By default, API Route handler functions will provide us with middleware \u2014 <code>bodyParser<\/code> under the hood that automatically parses the contents of the request body, cookies, and queries. We\u2019ll overwrite this behavior by exporting a config object to disable the built-in parsing mechanism since Multer does that for us.<\/p>\n<p>\nTo further enhance the uploading process with Vercel, you can try the following steps:\n<\/p>\n<ol>\n<li><strong>Create a Serverless Function to return a presigned URL:<\/strong> Instead of directly uploading to Cloudinary, you can use a presigned URL mechanism. This ensures that the upload URL is temporary and has a short lifespan, enhancing security.\n<li><strong>Vercel\u2019s Image Optimization:<\/strong> Remember, Vercel manages the infrastructure for uploading, optimizing, and delivering images. They optimize images based on factors like pixel density, format, size, and quality. These optimized images are cached on the Vercel Edge Network, ensuring faster delivery to end-users.\n<li><strong>Serverless Function Limit:<\/strong> Be aware of Vercel\u2019s serverless function limit of 4.5MB. It\u2019s advisable to avoid uploading large images directly. If you\u2019re integrating with other cloud providers, leverage their mechanisms, like the pre-signed URLs in AWS S3, to handle larger files.\n<li><strong>Vercel\u2019s Edge Network:<\/strong> This is both a Content Delivery Network (CDN) and a globally distributed platform for running compute at the edge. It ensures that your images are delivered quickly to users worldwide. This network stores content and runs compute in regions close to your customers or your data, which not only ensures quicker delivery of optimized images but also enhances the overall user experience by reducing latency.\n<h2>Building the Frontend<\/h2>\n<p>We\u2019ll now compose a simple user interface to communicate with the serverless function. Add the following to your <code>index.js<\/code> file:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> { useState } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react\"<\/span>;\n<span class=\"hljs-keyword\">const<\/span> UploadState = {\n  <span class=\"hljs-attr\">IDLE<\/span>: <span class=\"hljs-number\">1<\/span>,\n  <span class=\"hljs-attr\">UPLOADING<\/span>: <span class=\"hljs-number\">2<\/span>,\n  <span class=\"hljs-attr\">UPLOADED<\/span>: <span class=\"hljs-number\">3<\/span>,\n};\n<span class=\"hljs-built_in\">Object<\/span>.freeze(UploadState);\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Home<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> &#91;uploadState, setUploadState] = useState(UploadState.IDLE);\n  <span class=\"hljs-keyword\">const<\/span> &#91;imgUrl, setImgUrl] = useState(<span class=\"hljs-string\">\"\"<\/span>);\n  <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">handleFormData<\/span>(<span class=\"hljs-params\">e<\/span>) <\/span>{\n    setUploadState(UploadState.UPLOADING);\n    <span class=\"hljs-keyword\">const<\/span> file = e.target.files&#91;<span class=\"hljs-number\">0<\/span>];\n    <span class=\"hljs-keyword\">const<\/span> formData = <span class=\"hljs-keyword\">new<\/span> FormData();\n    formData.append(<span class=\"hljs-string\">\"file\"<\/span>, file);\n    <span class=\"hljs-keyword\">const<\/span> res = <span class=\"hljs-keyword\">await<\/span> fetch(<span class=\"hljs-string\">\"\/api\/upload\"<\/span>, {\n      <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">\"POST\"<\/span>,\n      <span class=\"hljs-attr\">body<\/span>: formData,\n    });\n    <span class=\"hljs-keyword\">const<\/span> data = <span class=\"hljs-keyword\">await<\/span> res.json();\n    setImgUrl(data.secure_url);\n    setUploadState(UploadState.UPLOADED);\n  }\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"flex justify-center h-screen items-center\"<\/span>&gt;<\/span>\n      {uploadState !== UploadState.UPLOADED ? (\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"w-32\"<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">label<\/span>\n            <span class=\"hljs-attr\">htmlFor<\/span>=<span class=\"hljs-string\">\"image\"<\/span>\n            <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"block bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 text-center\"<\/span>\n          &gt;<\/span>\n            {uploadState === UploadState.UPLOADING ? (\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span>&gt;<\/span>Uploading...<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n            ) : (\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span>&gt;<\/span>Upload<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n            )}\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">input<\/span>\n              <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"file\"<\/span>\n              <span class=\"hljs-attr\">name<\/span>=<span class=\"hljs-string\">\"file\"<\/span>\n              <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"image\"<\/span>\n              <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"hidden\"<\/span>\n              <span class=\"hljs-attr\">onChange<\/span>=<span class=\"hljs-string\">{handleFormData}<\/span>\n            \/&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">label<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n      ) : (\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"w-96 text-green-500 \"<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"block py-2 px-3 text-green-500 text-center\"<\/span>&gt;<\/span>\n            Uploaded!\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"w-full\"<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{imgUrl}<\/span> <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">\"Uploaded image\"<\/span> \/&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n      )}\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n  );\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This component allows users to select an image from their computer and gives them a button to upload the image, and it displays the image after a successful upload.<\/p>\n<p>We\u2019ll create a function called <code>handleFormData<\/code>, which embeds the file selected by the user into the request payload using the FormData API. Note that the string passed to the <code>formData.append()<\/code> method must be the same as the string we specified in our Multer instance in the API file.<\/p>\n<p>The payload is then used to initiate an HTTP request to the serverless function we created. It then sets the state of <code>imgURL<\/code> as well as <code>uploadState<\/code>.<\/p>\n<p>In the JSX returned, we\u2019ll render an upload button. If an image is uploaded successfully, we\u2019ll render the image instead.<\/p>\n<p>Now you can start your application on <a href=\"http:\/\/localhost:3000\/\">http:\/\/localhost:3000\/<\/a> with the following command:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">npm run dev\n<\/code><\/span><\/pre>\n<p>Once the app is up and running, you should be able to select a file and upload it. Here\u2019s what mine looks like after a successful upload.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/media_jams\/s_6399234270E32E86954C834839875C7D66DECC144A068FA9F3D11325803EA9A5_1655141151314_CleanShot+2022-06-13+at+18.25.25.gif\" alt=\"\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"800\" height=\"402\"\/><\/p>\n<p>Find the complete project <a href=\"https:\/\/github.com\/ifeoma-imoh\/vercel-serverless-upload\">here<\/a> on GitHub.<\/p>\n<h2>Conclusion<\/h2>\n<p>This article walks you through the process of uploading images to Cloudinary using Next.js and Vercel\u2019s Serverless Functions. With Next.js API routes, we can write server-side logic within our <a href=\"https:\/\/nextjs.org\/\">Next.js<\/a> applications, which can then be deployed as <a href=\"https:\/\/vercel.com\/docs\/concepts\/functions\/serverless-functions\">Serverless Functions<\/a> to <a href=\"https:\/\/vercel.com\/docs?redirected=1\">Vercel<\/a>. See <a href=\"https:\/\/vercel.com\/docs\/concepts\/functions\/serverless-functions#deploying-serverless-functions\">here<\/a> for more on how to deploy a Next.js API route as a serverless function to Vercel.<\/p>\n<p>##Resources<\/p>\n<ul>\n<li>\n<a href=\"https:\/\/vercel.com\/docs\/concepts\/functions\/serverless-functions\">Vercel Serverless Functions<\/a>\n<\/li>\n<li>\n<a href=\"https:\/\/nextjs.org\/docs\/api-routes\/introduction\">Next.js API routes<\/a>\n<\/li>\n<li>\n<a href=\"https:\/\/cloudinary.com\/documentation\/node_integration\">Cloudinary Node.js SDK<\/a>\n<\/li>\n<li>\n<a href=\"https:\/\/nextjs.org\/docs\/api-routes\/api-middlewares\">Next.js API middlewares<\/a>\n<\/li>\n<\/ul>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":41,"featured_media":30803,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[134,370,175,380,373],"class_list":["post-27981","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-guest-post","tag-image","tag-jamstack","tag-serverless","tag-upload"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.6 (Yoast SEO v26.9) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Upload Images With Vercel Serverless Functions<\/title>\n<meta name=\"description\" content=\"In this article, we&#039;ll take a look at the process of uploading images from a Next.js application to Cloudinary using Vercel&#039;s Serverless Functions.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/cloudinary.com\/blog\/upload-images-with-vercel-serverless-functions\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Upload Images With Vercel Serverless Functions\" \/>\n<meta property=\"og:description\" content=\"In this article, we&#039;ll take a look at the process of uploading images from a Next.js application to Cloudinary using Vercel&#039;s Serverless Functions.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/upload-images-with-vercel-serverless-functions\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2023-08-07T14:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-06-26T18:00:46+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/v1690225773\/Blog-image_upload_vercel\/Blog-image_upload_vercel-jpg?_i=AA\" \/>\n\t<meta property=\"og:image:width\" content=\"2000\" \/>\n\t<meta property=\"og:image:height\" content=\"1100\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"NewsArticle\",\"@id\":\"https:\/\/cloudinary.com\/blog\/upload-images-with-vercel-serverless-functions#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/upload-images-with-vercel-serverless-functions\"},\"author\":{\"name\":\"\",\"@id\":\"\"},\"headline\":\"Upload Images With Vercel Serverless Functions\",\"datePublished\":\"2023-08-07T14:00:00+00:00\",\"dateModified\":\"2025-06-26T18:00:46+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/upload-images-with-vercel-serverless-functions\"},\"wordCount\":6,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/upload-images-with-vercel-serverless-functions#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1690225773\/Blog-image_upload_vercel\/Blog-image_upload_vercel.jpg?_i=AA\",\"keywords\":[\"Guest Post\",\"Image\",\"JAMStack\",\"Serverless\",\"Upload\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2023\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/upload-images-with-vercel-serverless-functions\",\"url\":\"https:\/\/cloudinary.com\/blog\/upload-images-with-vercel-serverless-functions\",\"name\":\"Upload Images With Vercel Serverless Functions\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/upload-images-with-vercel-serverless-functions#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/upload-images-with-vercel-serverless-functions#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1690225773\/Blog-image_upload_vercel\/Blog-image_upload_vercel.jpg?_i=AA\",\"datePublished\":\"2023-08-07T14:00:00+00:00\",\"dateModified\":\"2025-06-26T18:00:46+00:00\",\"description\":\"In this article, we'll take a look at the process of uploading images from a Next.js application to Cloudinary using Vercel's Serverless Functions.\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/upload-images-with-vercel-serverless-functions#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/upload-images-with-vercel-serverless-functions\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/upload-images-with-vercel-serverless-functions#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1690225773\/Blog-image_upload_vercel\/Blog-image_upload_vercel.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1690225773\/Blog-image_upload_vercel\/Blog-image_upload_vercel.jpg?_i=AA\",\"width\":2000,\"height\":1100},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/upload-images-with-vercel-serverless-functions#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Upload Images With Vercel Serverless Functions\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"name\":\"Cloudinary Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/cloudinary.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\",\"name\":\"Cloudinary Blog\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"width\":312,\"height\":60,\"caption\":\"Cloudinary Blog\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Upload Images With Vercel Serverless Functions","description":"In this article, we'll take a look at the process of uploading images from a Next.js application to Cloudinary using Vercel's Serverless Functions.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/cloudinary.com\/blog\/upload-images-with-vercel-serverless-functions","og_locale":"en_US","og_type":"article","og_title":"Upload Images With Vercel Serverless Functions","og_description":"In this article, we'll take a look at the process of uploading images from a Next.js application to Cloudinary using Vercel's Serverless Functions.","og_url":"https:\/\/cloudinary.com\/blog\/upload-images-with-vercel-serverless-functions","og_site_name":"Cloudinary Blog","article_published_time":"2023-08-07T14:00:00+00:00","article_modified_time":"2025-06-26T18:00:46+00:00","og_image":[{"width":2000,"height":1100,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/v1690225773\/Blog-image_upload_vercel\/Blog-image_upload_vercel-jpg?_i=AA","type":"image\/jpeg"}],"twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/cloudinary.com\/blog\/upload-images-with-vercel-serverless-functions#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/upload-images-with-vercel-serverless-functions"},"author":{"name":"","@id":""},"headline":"Upload Images With Vercel Serverless Functions","datePublished":"2023-08-07T14:00:00+00:00","dateModified":"2025-06-26T18:00:46+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/upload-images-with-vercel-serverless-functions"},"wordCount":6,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/upload-images-with-vercel-serverless-functions#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1690225773\/Blog-image_upload_vercel\/Blog-image_upload_vercel.jpg?_i=AA","keywords":["Guest Post","Image","JAMStack","Serverless","Upload"],"inLanguage":"en-US","copyrightYear":"2023","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/upload-images-with-vercel-serverless-functions","url":"https:\/\/cloudinary.com\/blog\/upload-images-with-vercel-serverless-functions","name":"Upload Images With Vercel Serverless Functions","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/upload-images-with-vercel-serverless-functions#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/upload-images-with-vercel-serverless-functions#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1690225773\/Blog-image_upload_vercel\/Blog-image_upload_vercel.jpg?_i=AA","datePublished":"2023-08-07T14:00:00+00:00","dateModified":"2025-06-26T18:00:46+00:00","description":"In this article, we'll take a look at the process of uploading images from a Next.js application to Cloudinary using Vercel's Serverless Functions.","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/upload-images-with-vercel-serverless-functions#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/upload-images-with-vercel-serverless-functions"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/upload-images-with-vercel-serverless-functions#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1690225773\/Blog-image_upload_vercel\/Blog-image_upload_vercel.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1690225773\/Blog-image_upload_vercel\/Blog-image_upload_vercel.jpg?_i=AA","width":2000,"height":1100},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/upload-images-with-vercel-serverless-functions#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Upload Images With Vercel Serverless Functions"}]},{"@type":"WebSite","@id":"https:\/\/cloudinary.com\/blog\/#website","url":"https:\/\/cloudinary.com\/blog\/","name":"Cloudinary Blog","description":"","publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/cloudinary.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/cloudinary.com\/blog\/#organization","name":"Cloudinary Blog","url":"https:\/\/cloudinary.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","width":312,"height":60,"caption":"Cloudinary Blog"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":""}]}},"jetpack_featured_media_url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1690225773\/Blog-image_upload_vercel\/Blog-image_upload_vercel.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/27981","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/users\/41"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/comments?post=27981"}],"version-history":[{"count":15,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/27981\/revisions"}],"predecessor-version":[{"id":37832,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/27981\/revisions\/37832"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/30803"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=27981"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=27981"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=27981"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}