Uploading and Displaying Videos with Next.js

Next.js is a popular open-source React based frontend framework created and maintained by Vercel. It's a prominent player in the world of Jamstack with it's server-side rendering and static site generation capabilities.

In this article we take a look at how to upload videos using Next.js and Vercel's serverless functions.

Demo

You can use the Codesandbox below to see a working demo of the project.

If you plan on forking the sample, please bear in mind that you will need to change the api keys under pages/api/upload.js to match the details of your own Cloudinary account.

Note that due to file upload size limitations in place on Codesandbox, you can only upload a video with a small size - you can use this video if you don't have access to a small one.. If you run the project locally, there are no strict file size upload limits.

Click on Open Sandbox above to see the code for the project.

Starting with the project

Apart from Next.js, the project uses TailwindCSS, Formidable and Cloudinary and therefore after initialising the project these dependencies need to be setup as well:

npx create-next-app myApp --use-npm
npm i tailwindcss@latest postcss@latest autoprefixer@latest formidable cloudinary

To fully setup TailwindCSS in a Next.js environment, follow this tutorial.

Once the project is setup, we can start by creating a serverless function.

Vercel Serverless Function

Serverless functions are widely available and they have great use-cases in Jamstack applications, since we do not have access to an environment where server-side code execution is possible, we need to resort to utilising serverless functions. For the project at hand, we need to create a serverless function that will take a file (a video) coming from the frontend, and upload that to Cloudinary.

Upload to Cloudinary

Cloudinary has a Node.js SDK, which we can utilise to upload the video. But somehow we need to receive the video file. We'll take a look at the frontend implementation in a moment, but we need to do 3 things in the serverless function:

  • Overwrite the Vercel serverless function behaviour - which by default uses bodyParser,
  • Add formidable to be able to access the uploaded files, and
  • Upload the file to Cloudinary

The file found under pages/api/upload.js looks like this:

import cloudinary from 'cloudinary';
import { IncomingForm } from 'formidable';

cloudinary.config({
cloud_name: 'CLOUDINARY-USER-NAME',
api_key: 'CLOUDINARY-API-KEY',
api_secret: 'CLOUDINARY-API-SECRET',
});

export const config = {
api: {
bodyParser: false,
},
};

export default async (req, res) => {
const data = await new Promise((resolve, reject) => {
const form = new IncomingForm();

form.parse(req, (err, fields, files) => {
if (err) return reject(err);
resolve({ fields, files });
});
});

const file = data?.files?.inputFile.path;

const response = await cloudinary.v2.uploader.upload(file, {
resource_type: 'video',
public_id: 'my_video',
});
return res.json(response);
};

In the code above, we can see that bodyParser: false is set, form data is parsed by new IncomingForm() and last but not least the video is uploaded via cloudinary.v2.uploader.upload().

At this point testing the serverless function is possible via a tool such as Postman or Insomnia and by running vercel dev from the CLI. Make sure that when adding a video via the aforementioned tools, you add using the inputFile key, since that is what we are referencing here: const file = data?.files?.inputFile.path;.

The Frontend

Once the serverless function is done, we can start working on the frontend by modifying index.js and adding one of the three components that we are going to create:

import { Upload } from '../components/Upload';
// ...
<Upload />;
// ...

The components

As part of the project I have decided to create 3 components for all the functionality that I need. Since we are tapping into React world, there are many possible solutions available at your disposal, feel free to change things around to match your style of "React coding".

Upload.js

The upload component displays a file selector as part of a styled form (via TailwindCSS). Since we want to upload a file, an event handler needs to be added to the form, which will be the onChange handler. Putting all this together, this is how the simplied code looks like:

export functin Upload() {
const onChange = async (event) => {
// handle the upload
}

return (<input type="file" onChange={onChange} />)
}

The question is of course, what goes into the event handler. Since we are talking about a file upload, we need to utilise the FormData WebAPI:

const onChange = async (event) => {
event.preventDefault();
const formData = new FormData();
const file = event.target.files[0];
formData.append('inputFile', file);
};

Once we have the formData, it's time to send it to the serverless function created earlier:

try {
const response = await fetch('/api/upload', {
method: 'POST',
body: formData,
});
const data = await response.json();
} catch (error) {
//
} finally {
//
}

Notice that I am using the not-widely-known try-catch-finally block.

Video.js and Spinner.js

The other two components are purely cosmetic. Video.js is simple displaying and playing the video once it has been uploaded, while Spinner.js displays a spinner indicating the upload in progress.

These two components are being shown/hidden based on certain conditions that are enabled via React's useState and useEffect - check the code in Codesandbox for a full overview.

Wrap up

This small demonstrational project was fun to work on and there are a lot of directions that we could take it - we could transform the video using Cloudinary, we could call additional API services from the serverless function (maybe we could ask Cloudinary to transcribe the video, and send that off to a translation service). All in all, I hope this article was helpful and allowed you to learn something new.