# Add high-performance video to your Next.js application

Store, stream, and embed video in your Next.js application with the Mux API and components. Use this guide when you add video pages, user uploads, or background video to a Next.js site.

<Callout type="info">
  Mux is available as a native integration through the [Vercel Marketplace](https://vercel.com/marketplace/mux). Visit the [Vercel documentation](https://vercel.com/docs) for specific guidance related to getting up and running with Mux on Vercel.
</Callout>

## When should you use Mux with Next.js?

When adding video to your Next.js app, you'll encounter some common hurdles. First, videos are large. Storing them in your public directory can lead to excessive bandwidth consumption and poor Git repository performance. Next, it's important to compress and optimize your videos for the web. Then, as network conditions change, you might want to adapt the quality of your video to ensure a smooth playback experience for your users. Finally, you may want to integrate additional features like captions, thumbnails, and analytics.

You might consider using Mux's APIs and components to handle these challenges, [and more](https://www.mux.com/features).

## Quickly drop in a video with next-video

[`next-video`](https://next-video.dev) is a React component, [maintained by Mux](https://github.com/muxinc/next-video), for adding video to your Next.js application. It extends both the `<video>` element and your Next app with features to simplify video uploading, storage, and playback.

To get started...

1. Run the install script: `npx -y next-video init`. This will install the `next-video` package, update your `next.config.js` and TypeScript configuration, and create a `/videos` folder in your project.
2. Add a video to your `/videos` folder. Mux will upload, store, and optimize it for you.
3. Add the component to your app:

```jsx
import Video from 'next-video';
import myVideo from '/videos/my-video.mp4'; 
 
export default function Page() { 
 return <Video src={myVideo} />;
}
```

Check out the [`next-video` docs](https://next-video.dev/docs) to learn more.

## Use the API and our components for full control

If you're looking to build your own video workflow that enables uploading, playback, and more in your application, you can use the Mux API and components like [Mux Player](/docs/guides/mux-player-web) and [Mux Uploader](/docs/guides/mux-uploader).

### Example: allowing users to upload video to your app

One reason you might want to build your own video workflow is when you want to allow users to upload video to your app.

Let's start by adding a new page where users can upload videos. This will involve using the [Mux Uploader](/docs/guides/mux-uploader) component, which will upload videos to a Mux <ApiRefLink href="/docs/api-reference/video/direct-uploads/create-direct-upload">Direct Uploads URL</ApiRefLink>.

In the code sample below, we'll create an upload URL using the [Mux Node SDK](https://github.com/muxinc/mux-node-sdk) and the Direct Uploads URL API. We'll pass that URL to the Mux Uploader component, which will handle uploading for us.

```appDirJs

import Mux from '@mux/mux-node';
import MuxUploader from '@mux/mux-uploader-react';

const client = new Mux({
  tokenId: process.env['MUX_TOKEN_ID'],
  tokenSecret: process.env['MUX_TOKEN_SECRET'],
});

export default async function Page() {
  const directUpload = await client.video.uploads.create({
    cors_origin: '*',
    new_asset_settings: {
      playback_policy: ['public'],
    },
  });

  return <MuxUploader endpoint={directUpload.url} />;
}

```

```appDirTs

import Mux from '@mux/mux-node';
import MuxUploader from '@mux/mux-uploader-react';

const client = new Mux({
  tokenId: process.env['MUX_TOKEN_ID'],
  tokenSecret: process.env['MUX_TOKEN_SECRET'],
});

export default async function Page() {
  const directUpload = await client.video.uploads.create({
    cors_origin: '*',
    new_asset_settings: {
      playback_policy: ['public'],
    },
  });

  return <MuxUploader endpoint={directUpload.url} />;
}

```

```pagesDirJs

import Mux from '@mux/mux-node';
import MuxUploader from '@mux/mux-uploader-react';

const client = new Mux({
  tokenId: process.env['MUX_TOKEN_ID'],
  tokenSecret: process.env['MUX_TOKEN_SECRET'],
});

export const getServerSideProps = async () => {
  const directUpload = await client.video.uploads.create({
    cors_origin: '*',
    new_asset_settings: {
      playback_policy: ['public'],
    },
  });

  return {
    props: {
      directUpload,
    },
  };
}

export default function Page({ directUpload }) {
  return <MuxUploader endpoint={directUpload.url} />;
}
```

```pagesDirTs

import type { InferGetServerSidePropsType, GetServerSideProps } from 'next'

import Mux, { type Upload } from '@mux/mux-node';
import MuxUploader from '@mux/mux-uploader-react';

const client = new Mux({
  tokenId: process.env['MUX_TOKEN_ID'],
  tokenSecret: process.env['MUX_TOKEN_SECRET'],
});

export const getServerSideProps = (async () => {
  const directUpload = await client.video.uploads.create({
    cors_origin: '*',
    new_asset_settings: {
      playback_policy: ['public'],
    },
  });

  return {
    props: {
      directUpload,
    },
  };
}) satisfies GetServerSideProps<{ directUpload: Upload }>

export default function Page({
  directUpload
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
  return <MuxUploader endpoint={directUpload.url} />;
}
```



<Callout type="warning">
  In production, you'll want to apply additional security measures to your upload URL. Consider protecting the route with authentication to prevent unauthorized users from uploading videos. Also, use `cors_origin` and consider [`playback_policy`](/docs/guides/secure-video-playback) to further restrict where uploads can be performed and who can view uploaded videos.
</Callout>

Next, we'll create an API endpoint that will [listen for Mux webhooks](/docs/core/listen-for-webhooks). When we receive the notification that the video has finished uploading and is ready for playback, we'll add the video's metadata to our database.

```appDirJs

export async function POST(request) {
  const body = await request.json();
  const { type, data } = body

  if (type === 'video.asset.ready') {
    await saveAssetToDatabase(data);
  } else {
    /* handle other event types */
  }
  return Response.json({ message: 'ok' });
}

```

```appDirTs

export async function POST(request: Request) {
  const body = await request.json();
  const { type, data } = body

  if (type === 'video.asset.ready') {
    await saveAssetToDatabase(data);
  } else {
    /* handle other event types */
  }
  return Response.json({ message: 'ok' });
}

```

```pagesDirJs

export default async function muxWebhookHandler (req, res) {
  const { method, body } = req;

  switch (method) {
    case 'POST': {
      const { data, type } = body;

      if (type === 'video.asset.ready') {
        await saveAssetToDatabase(data);
      } else {
        /* handle other event types */
      }
      res.json({ message: ok });
    } default:
      res.setHeader('Allow', ['POST']);
      res.status(405).end(`Method ${method} Not Allowed`);
  }
}

```

```pagesDirTs

import { NextApiRequest, NextApiResponse } from 'next';

export default async function muxWebhookHandler (req: NextApiRequest, res: NextApiResponse): Promise<void> {
  const { method, body } = req;

  switch (method) {
    case 'POST': {
      const { data, type } = body;

      if (type === 'video.asset.ready') {
        await saveAssetToDatabase(data);
      } else {
        /* handle other event types */
      }
      res.json({ message: ok });
    } default:
      res.setHeader('Allow', ['POST']);
      res.status(405).end(`Method ${method} Not Allowed`);
  }
}

```



Finally, let's make a playback page. We retrieve the video metadata from our database, and play it by passing its `playbackId` to [Mux Player](/docs/guides/mux-player-web):

```appDirJs

import Mux from '@mux/mux-node';
import MuxPlayer from '@mux/mux-player-react';

const mux = new Mux();

export default async function Page({ params }) {
  /* Get the asset metadata from your database here or directly from Mux like below. */
  const asset = await mux.video.assets.retrieve(params.id);
  return <MuxPlayer playbackId={asset.playback_ids?.[0].id} accentColor="#ac39f2" />;
}

```

```appDirTs

import Mux from '@mux/mux-node';
import MuxPlayer from '@mux/mux-player-react';

const mux = new Mux();

export default async function Page({ params }: { params: { id: string } }) {
  /* Get the asset metadata from your database here or directly from Mux like below. */
  const asset = await mux.video.assets.retrieve(params.id);
  return <MuxPlayer playbackId={asset.playback_ids?.[0].id!} accentColor="#ac39f2" />;
}

```

```pagesDirJs

import Mux from '@mux/mux-node';
import MuxPlayer from '@mux/mux-player-react';

const mux = new Mux();

export const getStaticProps = async ({ params })  => {
  /* Get the asset metadata from your database here or directly from Mux like below. */
  const asset = await mux.video.assets.retrieve(params.id);
  return {
    props: {
      asset,
    },
  };
}

export default function Page({ asset }) {
  return <MuxPlayer playbackId={asset.playback_ids?.[0].id} accentColor="#ac39f2" />;
}

```

```pagesDirTs

import type { InferGetStaticPropsType, GetStaticProps } from 'next';

import Mux from '@mux/mux-node';
import MuxPlayer from '@mux/mux-player-react';

const mux = new Mux();

export const getStaticProps = (async ({ params }) => {
  /* Get the asset metadata from your database here or directly from Mux like below. */
  const asset = await mux.video.assets.retrieve(params.id);
  return {
    props: {
      asset,
    },
  };
}) satisfies GetStaticProps<{ asset: ReturnType<typeof mux.video.assets.retrieve> }>;

export default function Page({
  asset
}: InferGetStaticPropsType<typeof getStaticProps>) {
  return <MuxPlayer playbackId={asset.playback_ids?.[0].id!} accentColor="#ac39f2" />;
}

```



And we've got upload and playback. Nice!

What's next? You can [integrate with your CMS](/docs/integrations/cms). You can [optimize your loading experience](/docs/guides/player-lazy-loading). Or get started with an example project below:

## Example projects

<GuideCard
  title="Video Course Starter Kit"
  description={<p>If you’re a developer you’ve probably seen and used platforms like <a href="https://egghead.io/">Egghead</a>, <a href="https://leveluptutorials.com/">LevelUp Tutorials</a>, <a href="https://www.coursera.org/">Coursera</a>, etc. This is your starter kit to build something like that with Next.js + Mux. Complete with Github OAuth, the ability to create courses, adding video lessons, progress tracking for viewers.</p>}
  links={[
    {
      title: "View project →",
      href: "https://github.com/muxinc/video-course-starter-kit",
    },
  ]}
/>

<GuideCard
  title="with-mux-video"
  description={<>
    <p>This is a bare-bones starter application with Next.js that uses:</p>
    <ul>
      <li>Mux <ApiRefLink href="/docs/api-reference/video/direct-uploads">Direct Uploads</ApiRefLink></li>
      <li>Mux <a href="/docs/guides/video" title="Mux Video">Video</a> + Mux <a href="/docs/guides/data" title="Mux Data">Data</a></li>
      <li>Mux <a href="/docs/guides/mux-player-web" title="Mux Player">Player</a></li>
    </ul>
  </>}
  links={[
    {
      title: "View project →",
      href: "https://github.com/vercel/next.js/tree/931eee87be8af86bd95336deade5870ad5e04669/examples/with-mux-video",
    },
  ]}
/>

<GuideCard
  title="stream.new"
  description={<>
    <p>Stream.new is an open source Next.js application that does:</p>
    <ul>
      <li>Mux <ApiRefLink href="/docs/api-reference/video/direct-uploads">Direct Uploads</ApiRefLink></li>
      <li>Content Moderation with Google Vision or Hive.ai (<a href="https://www.mux.com/blog/you-either-die-an-mvp-or-live-long-enough-to-build-content-moderation">Read more</a>)</li>
    </ul>
  </>}
  links={[
    {
      title: "View project →",
      href: "https://github.com/muxinc/stream.new",
    },
  ]}
/>
