next-video
Next video is a react component for adding video to your next.js application. It extends both the <video>
element and your Next app with features for automatic video optimization.
import Video from 'next-video';
import getStarted from '/videos/get-started.mp4';
export default function Page() {
return <Video src={getStarted} />;
}
In the root of your Next.js project, run:
npx -y next-video init
This will (with prompting):
next-video
as a dependencynext.config.js
file/videos
directory in your project which is where you will put all video source files.It will also update your .gitignore
file to ignore video files in the /videos
directory. Videos, particularly any of reasonable size, shouldn't be stored/tracked by git. Alternatively, if you'd like to store the original files you can remove the added gitignore lines and install git-lfs.
Vercel recommends using a dedicated content platform for video because video files are large and can lead to excessive bandwidth usage. By default, next-video uses Mux (a video API for developers), which is built by the the creators of Video.js, powers popular streaming apps like Patreon, and whose video performance monitoring is used on the largest live events in the world.
.env.local
(or however you export local env variables)# .env.local
MUX_TOKEN_ID=[YOUR_TOKEN_ID]
MUX_TOKEN_SECRET=[YOUR_TOKEN_SECRET]
Add videos locally to the /videos
directory then run npx next-video sync
. The videos will be automatically uploaded to remote storage and optimized. You'll notice /videos/[file-name].json
files are also created. These are used to map your local video files to the new, remote-hosted video assets. These json files must be checked into git.
npx next-video sync
You can also add next-video sync -w
to the dev script to automatically sync videos as they're added to /videos
while the dev server is running.
// package.json
"scripts": {
"dev": "next dev & npx next-video sync -w",
},
Now you can use the <Video>
component in your application. Let's say you've added a file called awesome-video.mp4
to /videos
import Video from 'next-video';
import awesomeVideo from '/videos/awesome-video.mp4';
export default function Page() {
return <Video src={awesomeVideo} />;
}
While a video is being uploaded and processed, <Video>
will attempt to play the local file. This only happens during local development because the local file is never uploaded to your git repo.
For videos that are already hosted remotely (for example on AWS S3), import the remote URL and refresh the page.
This creates a local JSON file in the /videos
folder and the sync script will start uploading the video.
import Video from 'next-video';
import awesomeVideo from 'https://www.mydomain.com/remote-video.mp4';
export default function Page() {
return <Video src={awesomeVideo} />;
}
If the hosted video is a single file like an MP4, the file will be automatically optimized for better deliverability and compatibility.
Since v1.1.0
you can import the player component directly and use it without any upload and processing features.
import Player from 'next-video/player';
// or
import BackgroundPlayer from 'next-video/background-player';
export default function Page() {
return <Player
src="https://www.mydomain.com/remote-video.mp4"
poster="https://www.mydomain.com/remote-poster.webp"
blurDataURL="data:image/webp;base64,UklGRlA..."
/>;
}
You can add a custom poster and blurDataURL to the video by passing them as props.
import Video from 'next-video';
import awesomeVideo from '/videos/awesome-video.mp4';
import awesomePoster from '../public/images/awesome-poster.jpg';
export default function Page() {
return <Video
src={awesomeVideo}
poster={awesomePoster.src}
blurDataURL={awesomePoster.blurDataURL}
/>;
}
This is a good solution but it will not provide the same level of optimization as the automatic poster and blurDataURL by the default provider.
To get the same level of optimization you can use a slotted poster element.
Add a slotted poster image element
(like next/image
)
to the video by passing it as a child with a slot="poster"
attribute.
Now your image will get all the benefits of the used image component and it will be nicely placed behind the video player controls.
import Image from 'next/image';
import Video from 'next-video';
import awesomeVideo from '/videos/awesome-video.mp4';
import awesomePoster from '../public/images/awesome-poster.jpg';
export default function Page() {
return (
<Video src={awesomeVideo}>
<Image
slot="poster"
src={awesomePoster}
placeholder="blur"
alt="Some peeps doing something awesome"
/>
</Video>
);
}
You can customize the player by passing a custom player component to the as
prop.
The custom player component accepts the following props:
asset
: The asset that is processed, contains useful asset metadata and upload status.src
: A string video source URL if the asset is ready.poster
: A string image source URL if the asset is ready.blurDataURL
: A string base64 image source URL that can be used as a placeholder.import Video from 'next-video';
import ReactPlayer from './player';
import awesomeVideo from '/videos/awesome-video.mp4';
export default function Page() {
return <Video as={ReactPlayer} src={awesomeVideo} />;
}
// player.tsx
'use client';
import type { PlayerProps } from 'next-video';
import ReactPlayer from 'react-player';
export default function Player(props: PlayerProps) {
let { asset, src, poster, blurDataURL, thumbnailTime, ...rest } = props;
let config = { file: { attributes: { poster } } };
return <ReactPlayer
url={src}
config={config}
width="100%"
height="100%"
{...rest}
/>;
}
You can use a <BackgroundVideo>
component to add a video as a background with
no player controls. This saves about 50% of the JS player size and is optimized
for background video usage.
The <BackgroundVideo>
component is a custom player like you saw in the previous section.
The thumbnailTime
query parameter in the example below is used to generate
a poster image and blur up image at the specified time in the video
(limited to usage with the mux
provider).
import BackgroundVideo from 'next-video/background-video';
import getStarted from '/videos/country-clouds.mp4?thumbnailTime=0';
export default function Page() {
return (
<BackgroundVideo src={getStarted}>
<h1>next-video</h1>
<p>
A React component for adding video to your Next.js application.
It extends both the video element and your Next app with features
for automatic video optimization.
</p>
</BackgroundVideo>
);
}
You can choose between different providers for video processing and hosting.
The default provider is Mux.
To change the provider you can add a provider
option in the next-video config.
Some providers require additional configuration which can be passed in the providerConfig
property.
// next.config.js
const { withNextVideo } = require('next-video/process');
/** @type {import('next').NextConfig} */
const nextConfig = {};
module.exports = withNextVideo(nextConfig, {
provider: 'backblaze',
providerConfig: {
backblaze: { endpoint: 'https://s3.us-west-000.backblazeb2.com' }
}
});
Supported providers with their required environment variables:
Provider | Environment vars | Provider config | Pricing link |
---|---|---|---|
mux (default) |
MUX_TOKEN_ID MUX_TOKEN_SECRET |
Pricing | |
vercel-blob |
BLOB_READ_WRITE_TOKEN |
Pricing | |
backblaze |
BACKBLAZE_ACCESS_KEY_ID BACKBLAZE_SECRET_ACCESS_KEY |
endpoint bucket (optional) |
Pricing |
amazon-s3 |
AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY |
endpoint bucket (optional) |
Pricing |
Mux (default) | Vercel Blob | Backblaze | Amazon S3 | |
---|---|---|---|---|
Off-repo storage | ✅ | ✅ | ✅ | ✅ |
Delivery via CDN | ✅ | ✅ | - | - |
BYO player | ✅ | ✅ | ✅ | ✅ |
Compressed for streaming | ✅ | - | - | - |
Adapt to slow networks (HLS) | ✅ | - | - | - |
Automatic placeholder poster | ✅ | - | - | - |
Timeline hover thumbnails | ✅ | - | - | - |
Stream any source format | ✅ | * | * | * |
AI captions & subtitles | ✅ | - | - | - |
Video analytics | ✅ | - | - | - |
Pricing | Minutes-based | GB-based | GB-based | GB-based |
*Web-compatible MP4 files required for hosting providers without video processing
By default the asset metadata is stored in a JSON file in the /videos
directory.
If you want to store the metadata in a database or elsewhere you can customize
the storage hooks in a separate next-video config file.
The below example config shows the default storage hooks for the JSON file storage.
These hooks can be customized to fit your needs by changing the body of the
loadAsset
, saveAsset
, and updateAsset
functions.
// next-video.mjs
import { NextVideo } from 'next-video/process';
import path from 'node:path';
import { mkdir, readFile, writeFile } from 'node:fs/promises';
export const { GET, POST, handler, withNextVideo } = NextVideo({
// Other next-video config options should be added here if using a next-video config file.
// folder: 'videos',
// path: '/api/video',
loadAsset: async function (assetPath) {
const file = await readFile(assetPath);
const asset = JSON.parse(file.toString());
return asset;
},
saveAsset: async function (assetPath, asset) {
try {
await mkdir(path.dirname(assetPath), { recursive: true });
await writeFile(assetPath, JSON.stringify(asset), {
flag: 'wx',
});
} catch (err) {
if (err.code === 'EEXIST') {
// The file already exists, and that's ok in this case. Ignore the error.
return;
}
throw err;
}
},
updateAsset: async function (assetPath, asset) {
await writeFile(assetPath, JSON.stringify(asset));
}
});
Then import the withNextVideo
function in your next.config.mjs
file.
// next.config.mjs
import { withNextVideo } from './next-video.mjs';
/** @type {import('next').NextConfig} */
const nextConfig = {};
export default withNextVideo(nextConfig);
Lastly import the GET
and POST
, or handler
functions in your API routes as you see fit.
The handlers expect a url
query or body parameter with the video source URL.
These are the most minimal examples for the handlers, typically you would add more error handling and validation, authentication and authorization.
App router (Next.js >=13)
// app/api/video/route.js
export { GET, POST } from '@/next-video';
Pages router (Next.js)
// pages/api/video/[[...handler]].js
export { handler as default } from '@/next-video';
If you want to develop on this thing locally, you can clone and link this sucker. Just know...it's not a great time right now.
cd
into the reponpm install && npm run build
cd ../
(or back to wherever you want to create a test app)npx create-next-app
cd your-next-app
npx link ../next-video
(or wherever you cloned this repo)