Uploading and Displaying Videos with Next.js
Older Article
This article was published 5 years ago. Some information may be outdated or no longer applicable.
Next.js is a popular open-source React-based frontend framework created and maintained by Vercel. It’s a big name in the Jamstack world thanks to its server-side rendering and static site generation capabilities.
Let’s build a project that uploads videos using Next.js and Vercel’s serverless functions.
Demo
Check out the working demo in the Codesandbox below.
If you fork the sample, you’ll need to swap the API keys under
pages/api/upload.jswith your own Cloudinary account details.
Codesandbox has file upload size limits, so you can only upload a small video there. Grab this one if you don’t have a small file handy. Running the project locally removes that restriction.
Click
Open Sandboxabove to see the full code.
Starting with the project
Beyond Next.js, the project uses TailwindCSS, Formidable, and Cloudinary. After initialising the project, set up those dependencies:
npx create-next-app myApp --use-npm
npm i tailwindcss@latest postcss@latest autoprefixer@latest formidable cloudinary
For the full TailwindCSS setup in a Next.js project, follow this tutorial.
Once everything’s in place, we can build the serverless function.
Vercel Serverless Function
Serverless functions are a natural fit for Jamstack apps. Since there’s no server-side environment available, you offload that work to serverless functions. For this project, we need one that takes a video from the frontend and uploads it to Cloudinary.
Upload to Cloudinary
Cloudinary has a Node.js SDK we can use for the upload. But we also need to receive the video file. We’ll look at the frontend in a moment. In the serverless function, three things need to happen:
- Override Vercel’s default
bodyParserbehaviour - Add
formidableto access uploaded files - Upload the file to Cloudinary
Here’s pages/api/upload.js:
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);
};
Notice bodyParser: false is set, form data is parsed by new IncomingForm(), and the video gets uploaded via cloudinary.v2.uploader.upload().
You can test the serverless function with a tool like Postman or Insomnia by running
vercel devfrom the CLI. When sending a video through those tools, use theinputFilekey, since that’s what the code references:const file = data?.files?.inputFile.path;.
The Frontend
With the serverless function sorted, let’s build the frontend. Modify index.js and drop in the Upload component:
import { Upload } from '../components/Upload';
// ...
<Upload />;
// ...
The components
I’ve split the functionality into 3 components. Since we’re in React territory, there are plenty of ways to structure this. Feel free to rearrange things to match how you like to write React.
Upload.js
The upload component shows a styled file selector (via TailwindCSS). Since we’re uploading a file, we need an onChange handler on the form. Here’s the simplified version:
export functin Upload() {
const onChange = async (event) => {
// handle the upload
}
return (<input type="file" onChange={onChange} />)
}
What goes inside the event handler? Since it’s a file upload, we reach for 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’ve got the formData, fire it off to the serverless function:
try {
const response = await fetch('/api/upload', {
method: 'POST',
body: formData,
});
const data = await response.json();
} catch (error) {
//
} finally {
//
}
Notice the try-catch-finally block. It doesn’t get enough love.
Video.js and Spinner.js
The other two components are cosmetic. Video.js displays and plays the video after upload. Spinner.js shows a loading indicator while the upload’s in progress.
Both components toggle visibility based on state managed through React’s useState and useEffect. Check the Codesandbox code for the full picture.
Wrap up
This was a fun little project to put together. You could take it in all sorts of directions: transform the video with Cloudinary, call additional APIs from the serverless function (maybe transcribe the video and send the text to a translation service). Hope you picked up something new.