Skip to content

How to Resize Videos for Social Media in Next.js

In recent years, video publishing has progressed rapidly, with new platforms and tools constantly being introduced. According to an article by Statista, the number of digital video viewers worldwide is more than 3.3 billion. This shift has made sharing video content simpler than ever; however, creating and managing videos across these platforms still requires editing to specific dimensions and formatting. For example, platforms like Instagram and YouTube have different size requirements depending on the channel, e.g., feed, reels, shorts, etc. This means you must find tools to format your videos for each platform.

By building a video resize app, you can customize videos for different social media platforms with ease. This blog post walks through how to build an app with Next.js and Cloudinary.

Before we start, you should have a basic understanding of TypeScript or JavaScript and a free Cloudinary account. Also, you can find the completed app in this GitHub repository.

To create a Next.js project, run the command below and select Yes for Typescript and App Router, as that’s what you‘ll build this app in:

<code>$ npx create-next-app@latest</code>Code language: HTML, XML (xml)

Next, in the project directory, install the Cloudinary package with this command:

<code>$ npm install cloudinary</code>Code language: HTML, XML (xml)

Then, run npm run dev to render a development server at https://localhost:3000/ in your browser.

In the project’s root directory, add a new file, .env.local, and paste the code below in that file. This file will house all your Cloudinary app keys.

<code>CLOUDINARY_CLOUD_NAME=<CLOUDINARY_CLOUD_NAME></code>
<code>CLOUDINARY_API_KEY=<CLOUDINARY_API_KEY></code>
<code>CLOUDINARY_API_SECRET=<CLOUDINARY_API_SECRET></code>Code language: HTML, XML (xml)

To get these details, navigate to the Cloudinary developer dashboard. You should see it under the Product Environment Credentials section as shown in the image below:

Cloudinary developer dashboard

To set up the Cloudinary library, add a new folder called lib in the src folder; in that folder, add a file and name it cloudinary.ts.

// cloudinary.ts

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 default cloudinary;Code language: JavaScript (javascript)

In the code above, you imported the v2 version of the Cloudinary library. The cloudinary.config() method configures the Cloudinary client with the account details you retrieved from your dashboard.

This setup allows other parts of the application to import and use the configured Cloudinary client without configuring it again.

In the page.module.css file, replace the boilerplate code with the code below to style the application.

.page {

  --gray-rgb: 0, 0, 0;

  --gray-alpha-200: rgba(var(--gray-rgb), 0.08);

  --gray-alpha-100: rgba(var(--gray-rgb), 0.05);

  --button-primary-hover: #383838;

  --button-secondary-hover: #f2f2f2;

  display: grid;

  grid-template-rows: 20px 1fr 20px;

  align-items: center;

  justify-items: center;

  min-height: 100svh;

  padding: 80px;

  gap: 64px;

  font-family: var(--font-geist-sans);

}

.main {

  display: flex;

  flex-direction: column;

  gap: 32px;

  grid-row-start: 2;

}

.upload-form {

  display: flex;

  flex-direction: column;

  gap: 1rem;

  width: auto;

  margin: 0 auto;

  padding: 2rem;

  border: 1px solid #ddd;

  border-radius: 4px;

  background-color: white;

}

.upload-form h2 {

  color: #484c48;

}

.form-group {

  display: flex;

  align-items: center;

  margin: 20px 0;

}

.form-group label {

  flex: 0 0 30%;

  text-align: left;

  color: #403e3e;

}

.form-group input,

.form-group select {

  flex: 1;

  padding: 0.5rem;

  border: 1px solid #ccc;

  border-radius: 4px;

  background: none;

  color: black;

}

.form-group input:active,

.form-group select:active {

  outline-style: none;

  outline-color: white;

}

.submit-button {

  background-color: rgb(81, 81, 234);

  color: white;

  padding: 0.5rem;

  border: none;

  border-radius: 4px;

  cursor: pointer;

  font-size: medium;

}

.submit-button:hover {

  background-color: rgb(119, 119, 243);

}Code language: CSS (css)

Now that you’re all set up, you can start building the upload and resize functionality. In the page.tsx file, you need to first set up the form that will upload your video to Cloudinary. You can also use the Cloudinary widget to upload an asset but you won’t be able to transform the asset on the fly.

Replace the boilerplate code in the file with this:

// src/app/page.tsx

import styles from './page.module.css';

import cloudinary from '@/lib/cloudinary';

import { SubmitButton } from './submit-button';

export default function Home() {

  const handleSubmit = async (formData: FormData) => {};

  return (

    <div className={styles.page}>

      <main className={styles.main}>

        <div className={styles['upload-form']}>

          <h2>Upload and Resize Your Video</h2>

          <form action={handleSubmit}>

            <div className={styles['form-group']}>

              <label htmlFor='video'>Upload Video:</label>

              <input type='file' name='video' accept='video/*' required />

            </div>

            <div className={styles['form-group']}>

              <label htmlFor='format'>Formats:</label>

              <select name='format'>

                <option value='9:16'>Instagram Reels</option>

                <option value='1:1'>Instagram Feed</option>

                <option value='9:16'>TikTok</option>

                <option value='9:16'>YouTube Shorts</option>

                <option value='16:9'>YouTube Standard</option>

                <option value='9:16'>Facebook Stories</option>

                <option value='1:1'>Facebook Feed</option>

                <option value='9:16'>Snapchat</option>

                <option value='1:1'>Twitter/X</option>

                <option value='1:1'>LinkedIn</option>

              </select>

            </div>

            <SubmitButton />

          </form>

        </div>

      </main>

    </div>

  );

}Code language: JavaScript (javascript)

The code above sets up the upload form with the various video platforms and their aspect ratios.

To implement the submit button component, create a new file in the app folder called submit-button.tsx and paste the code below in it. The button component acts as the submit button and also the loading indicator. When you submit the form, the loader shows, indicating that the file is uploading and processing on Cloudinary.

// src/app/submit-button.tsx

'use client';

import { useFormStatus } from 'react-dom';

import styles from './page.module.css';

export function SubmitButton() {

  const { pending } = useFormStatus();

  return (

    <>

      {!pending ? (

        <button type='submit' className={styles['submit-button']}>

          Upload Video!

        </button>

      ) : (

        <button className={styles['submit-button']}>

          Uploading and Processing your video{' '}

          <svg

            xmlns='http://www.w3.org/2000/svg'

            width='1em'

            height='1em'

            viewBox='0 0 24 24'

          >

            <circle cx={4} cy={12} r={3} fill='currentColor'>

              <animate

                id='svgSpinners3DotsBounce0'

                attributeName='cy'

                begin='0;svgSpinners3DotsBounce1.end+0.25s'

                calcMode='spline'

                dur='0.6s'

                keySplines='.33,.66,.66,1;.33,0,.66,.33'

                values='12;6;12'

              ></animate>

            </circle>

            <circle cx={12} cy={12} r={3} fill='currentColor'>

              <animate

                attributeName='cy'

                begin='svgSpinners3DotsBounce0.begin+0.1s'

                calcMode='spline'

                dur='0.6s'

                keySplines='.33,.66,.66,1;.33,0,.66,.33'

                values='12;6;12'

              ></animate>

            </circle>

            <circle cx={20} cy={12} r={3} fill='currentColor'>

              <animate

                id='svgSpinners3DotsBounce1'

                attributeName='cy'

                begin='svgSpinners3DotsBounce0.begin+0.2s'

                calcMode='spline'

                dur='0.6s'

                keySplines='.33,.66,.66,1;.33,0,.66,.33'

                values='12;6;12'

              ></animate>

            </circle>

          </svg>

        </button>

      )}

    </>

  );

}Code language: JavaScript (javascript)

In the file above, the use client derivative at the top of the file indicates that this is a client component, and it gives you access to client-side hooks like useFormStatus which is used to get the status of the form.

In the page.tsx file, add these import statements below the already imported statements.

...

import { redirect } from 'next/navigation';

import { UploadApiResponse } from 'cloudinary';Code language: JavaScript (javascript)

To get the data from the form, add the use server directive to the top of the handleSubmit function; this signals to Next.js that the enclosed code is meant to run on the server and it prevents the function from being exposed to the client, enhancing security and performance. 

Still in the handleSubmit function, retrieve the video file from the form data and cast it as a File object. Also, you’ll get the format from the form, casting it as a string. After this, you’ll need to convert the video file into a binary Buffer by reading the file’s content as an ArrayBuffer and then converting this Buffer into a Buffer object using Buffer.from(). This conversion is necessary for handling the binary data of the video file on the server. 

Finally, initialize an empty string variable url, which will store the URL of the processed video.

// src/app/page.tsx

'use server';

const file = formData.get('video') as File;

const aspect_ratio = formData.get('format') as string;

const buffer: Buffer = Buffer.from(await file.arrayBuffer());

let url: string = '';Code language: JavaScript (javascript)

The next step is to upload the video file from the form data to Cloudinary, crop it to the specified aspect ratio, and then redirect the app to the processed video URL. The process will be done within a try-catch block in the handleSubmitfunction, which ensures that any errors during the upload process are caught and logged for debugging purposes.

//src/app/page.tsx

....

try {

  const base64Image: string = `data:${file.type};base64,${buffer.toString(

    'base64'

  )}`;

  const uploadResult: UploadApiResponse = await cloudinary.uploader.upload(

    base64Image,

    {

        resource_type: 'video',

        transformation: [{ aspect_ratio, crop: 'fill' }],

        public_id: `videos/${Date.now()}`,

    }

  );

  url = uploadResult.secure_url;

 } catch (error: any) {

  console.error(error);

}

redirect(url);Code language: JavaScript (javascript)

The video file is first converted from a Buffer into a Base64-encoded string because this is what the Cloudinary uploader method expects, using the video’s MIME (Multipurpose Internet Mail Extensions) type and binary data via Buffer.toString('base64'), and prefixed with a data URI (Uniform Resource Identifier) scheme to create a valid Base64 string. This string is then uploaded to Cloudinary using the cloudinary.uploader.upload method, with the resource_type option set to “video” to ensure proper handling of the video file.

The transformation option is where the actual transformation of the video is done. The video is cropped to the aspect ratio retrieved from the form and a unique identifier is generated using the current timestamp to ensure that each uploaded video is unique and it is set to the public_id.

After the video is successfully uploaded and processed, the result returned by Cloudinary (uploadResult) includes the secure URL (uploadResult.secure_url) pointing to the uploaded video which is stored in the url variable. 

If errors occur during this process, they’re caught in the catch block and logged to the console. Learn more about Cloudinary asset upload in the documentation.

Finally, the app is redirected to the uploaded video’s URL using the redirect (url) function.

Your final code for the upload and process functionality in page.tsx file should look like this:

//src/app/page.tsx

import styles from './page.module.css';

import { UploadApiResponse } from 'cloudinary';

import cloudinary from '@/lib/cloudinary';

import { redirect } from 'next/navigation';

import { SubmitButton } from './submit-button';

export default function Home() {

  const handleSubmit = async (formData: FormData) => {

    'use server';

    const file = formData.get('video') as File;

    const aspect_ratio = formData.get('format') as string;

    const buffer: Buffer = Buffer.from(await file.arrayBuffer());

    let url: string = '';

    try {

      const base64Image: string = `data:${file.type};base64,${buffer.toString(

        'base64'

      )}`;

      const uploadResult: UploadApiResponse = await cloudinary.uploader.upload(

        base64Image,

        {

          resource_type: 'video',

          transformation: [{ aspect_ratio, crop: 'fill' }],

          public_id: `videos/${Date.now()}`,

        }

      );

      url = uploadResult.secure_url;

    } catch (error: any) {

      console.error(error);

    }

    redirect(url);

  };

  return (

   ...

  );

}Code language: JavaScript (javascript)

When you run the application, you can select a video to upload and choose from a list of aspect ratios to crop the video. The video is then sent to Cloudinary, and the transformation is done. Once that’s completed, your app will redirect to the transformed video as shown in the demo below.

In this blog post, you learned how to use Next.js and the powerful media tools provided by Cloudinary to build a simple video transformation app. The Cloudinary video management library is an incredible resource for even more video transformations. 

Try extending this app to support more aspect ratios and gain access to even more enhancements with Cloudinary’s video solution. You can also explore reusing the same video with on-the-fly aspect ratio transformations for multiple social media platforms.

If you enjoyed this post and want to discuss it in more detail, join the Cloudinary Community forum and its associated Discord.

Back to top

Featured Post