Skip to content

Adding video to your Next.js application

Video in Next.js looks simple at first—drop in a <video> tag and point it at an MP4. That works until you need adaptive bitrate streaming, fast startup times, or playback that doesn't crater Core Web Vitals. Then you're suddenly dealing with transcoding pipelines, HLS manifests, and CDN configuration.

This guide shows you how to add video to a Next.js application the straightforward way: using a video API that handles the infrastructure while you focus on the product.

LinkThe native approach (and its limits)

Next.js doesn't include a built-in video component the way it does for images. You use standard HTML:

// app/page.jsx export default function Page() { return ( <video width="640" height="360" controls> <source src="/videos/demo.mp4" type="video/mp4" /> </video> ); }

This works for small, single-file videos. But there are problems:

No adaptive bitrate: The browser downloads the full MP4 resolution regardless of connection speed or screen size. On a slow connection, viewers wait. On mobile, they burn data on unnecessary quality.

No global delivery: Your video serves from wherever your Next.js app is hosted. For a Vercel deployment, that might be one region. Viewers far from that region get slow load times.

Large bundle impact: Self-hosted MP4s in your /public folder ship with your build. A few large videos can significantly slow deployments.

Format limitations: MP4 with H.264 works broadly, but you miss modern codecs like VP9 or AV1 that deliver better quality at lower bitrates.

For production video, you want HLS (HTTP Live Streaming), which breaks video into small chunks at multiple quality levels. The player picks the right quality dynamically based on available bandwidth.

LinkHow Mux Video works with Next.js

Mux is a video API that handles encoding, storage, and delivery. You upload a video, Mux processes it into HLS with multiple renditions, and you get back a playback ID you can use anywhere.

The workflow:

  1. Upload video → Mux encodes it to adaptive HLS
  2. Get playback ID → A short string that identifies your video
  3. Embed with Mux Player → A React component optimized for performance

Here's what the integration looks like:

// app/components/VideoPlayer.jsx 'use client'; import MuxPlayer from '@mux/mux-player-react'; export default function VideoPlayer({ playbackId, title }) { return ( <MuxPlayer playbackId={playbackId} streamType="on-demand" metadata={{ video_title: title, }} /> ); }
// app/page.jsx import VideoPlayer from './components/VideoPlayer'; export default function Page() { return ( <main> <h1>Watch the demo</h1> <VideoPlayer playbackId="EcHgOK9coz5K4rjSwOkoE7Y7O01201YMIC200RI6lNxnhs" title="Product Demo" /> </main> ); }

The playbackId comes from the Mux API when you create an asset. Mux Player handles HLS playback, adaptive quality switching, and works across all browsers without additional configuration.

LinkInstalling Mux Player in Next.js

Add the React package to your project:

npm install @mux/mux-player-react

Mux Player is a client component—it needs to run in the browser—so you'll use the 'use client' directive when importing it:

'use client'; import MuxPlayer from '@mux/mux-player-react';

For Server Components in the App Router, you have two options:

Option 1: Create a client wrapper component (recommended)

// components/Player.jsx 'use client'; import MuxPlayer from '@mux/mux-player-react'; export default function Player(props) { return <MuxPlayer {...props} />; }

Then import this wrapper wherever you need video:

// app/video/[id]/page.jsx import Player from '@/components/Player'; export default async function VideoPage({ params }) { const video = await getVideo(params.id); return ( <article> <h1>{video.title}</h1> <Player playbackId={video.playbackId} streamType="on-demand" /> </article> ); }

Option 2: Use dynamic import with ssr: false

import dynamic from 'next/dynamic'; const MuxPlayer = dynamic( () => import('@mux/mux-player-react'), { ssr: false } );

LinkCreating a hero video with Mux autoplay

Hero sections with background video are common on marketing sites. The key requirements: autoplay, muted (required for autoplay to work), and looping.

Mux Player has extended autoplay options that handle browser restrictions gracefully:

'use client'; import MuxPlayer from '@mux/mux-player-react'; import styles from './HeroVideo.module.css'; export default function HeroVideo({ playbackId }) { return ( <section className={styles.hero}> <MuxPlayer playbackId={playbackId} autoPlay="muted" loop muted playsInline className={styles.video} style={{ '--controls': 'none', }} /> <div className={styles.content}> <h1>Build something great</h1> <p>Your hero copy here</p> </div> </section> ); }

The CSS module for full-bleed background video:

/* HeroVideo.module.css */ .hero { position: relative; width: 100%; height: 100vh; overflow: hidden; } .video { position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover; } .content { position: relative; z-index: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; color: white; text-align: center; }

The autoPlay="muted" option ensures the video starts muted (required by browsers) and the --controls: none CSS variable hides the player UI for a clean background effect.

LinkUsing next-video for simpler integration

For the most Next.js-native experience, there's next-video—a package that works like next/image but for video. It handles uploading to Mux automatically during development.

npm install next-video npx next-video init

This creates a /videos folder in your project. Drop a video file there:

/videos └── hero-loop.mp4

Then import it directly:

import Video from 'next-video'; import heroLoop from '/videos/hero-loop.mp4'; export default function HeroSection() { return ( <Video src={heroLoop} autoPlay muted loop playsInline controls={false} /> ); }

When you run your development server, next-video uploads the file to Mux and stores the playback ID locally. In production, it serves from Mux's CDN. You get the simplicity of local files with the performance of a video API.

LinkEmbedding adaptive HLS in Next.js with Mux

Under the hood, Mux Player streams video via HLS—the industry standard for adaptive streaming. You can also use the raw HLS URL directly if you need to integrate with other players:

https://stream.mux.com/{PLAYBACK_ID}.m3u8

For example, with Video.js or HLS.js:

'use client'; import { useEffect, useRef } from 'react'; import Hls from 'hls.js'; export default function HlsPlayer({ playbackId }) { const videoRef = useRef(null); useEffect(() => { const video = videoRef.current; const src = `https://stream.mux.com/${playbackId}.m3u8`; if (video.canPlayType('application/vnd.apple.mpegurl')) { // Safari supports HLS natively video.src = src; } else if (Hls.isSupported()) { // Use HLS.js for other browsers const hls = new Hls(); hls.loadSource(src); hls.attachMedia(video); } }, [playbackId]); return <video ref={videoRef} controls />; }

However, Mux Player bundles HLS.js internally with optimized configuration, so you get the best performance without managing the library yourself.

LinkIntegrating Mux with Vercel deployments

Mux is available in the Vercel Marketplace, making integration with Vercel-hosted Next.js apps straightforward:

  1. Go to your Vercel dashboard → Storage → Browse Marketplace
  2. Find Mux and connect it to your project
  3. Environment variables (MUX_TOKEN_ID, MUX_TOKEN_SECRET) are added automatically

Once connected, you can use the Mux API from your Next.js Server Actions or Route Handlers:

// app/api/videos/route.js import Mux from '@mux/mux-node'; const mux = new Mux(); export async function POST(request) { const { videoUrl } = await request.json(); const asset = await mux.video.assets.create({ input: [{ url: videoUrl }], playback_policy: ['public'], }); return Response.json({ playbackId: asset.playback_ids[0].id, status: asset.status, }); }

For direct uploads from the browser (user-generated content), create an upload URL server-side and pass it to the client:

// app/api/upload/route.js import Mux from '@mux/mux-node'; const mux = new Mux(); export async function POST() { const upload = await mux.video.uploads.create({ new_asset_settings: { playback_policy: ['public'], }, cors_origin: '*', }); return Response.json({ uploadUrl: upload.url, uploadId: upload.id, }); }

LinkAdding captions to your videos

Mux generates captions automatically during encoding when you enable the feature:

const asset = await mux.video.assets.create({ input: [{ url: videoUrl }], playback_policy: ['public'], auto_generated_captions: [{ language: 'en' }], });

Mux Player displays captions automatically when they're available. You can control their initial visibility:

<MuxPlayer playbackId={playbackId} defaultHiddenCaptions={false} // Show captions by default />

For custom caption files (WebVTT), use the text tracks API after the asset is created.

LinkPerformance considerations for Next.js

Video can impact Core Web Vitals if handled poorly. Here's how to keep things fast:

Use loading="lazy" for below-fold videos

Mux Player supports lazy loading to defer video initialization until the player enters the viewport:

<MuxPlayer playbackId={playbackId} loading="viewport" />

Provide poster images

Show a static image while the video loads. Mux generates these automatically:

<MuxPlayer playbackId={playbackId} poster={`https://image.mux.com/${playbackId}/thumbnail.jpg`} />

Consider preload settings

For hero videos that should start fast, use preload="auto". For other videos, preload="metadata" or preload="none" reduces initial bandwidth:

<MuxPlayer playbackId={playbackId} preload="metadata" />

Use Server Components for data fetching

Fetch video metadata (titles, playback IDs) in Server Components, then pass to client components:

// app/videos/page.jsx (Server Component) import { getVideos } from '@/lib/videos'; import VideoGrid from './VideoGrid'; export default async function VideosPage() { const videos = await getVideos(); return <VideoGrid videos={videos} />; } // VideoGrid.jsx (Client Component) 'use client'; import MuxPlayer from '@mux/mux-player-react'; export default function VideoGrid({ videos }) { // Render players with data from server }

LinkStreaming analytics with Mux Data

Mux Player automatically reports playback analytics to Mux Data. You get metrics like:

  • Video startup time
  • Rebuffering rate
  • Playback errors
  • Watch time and engagement

Add metadata to track custom dimensions:

<MuxPlayer playbackId={playbackId} metadata={{ video_id: 'product-demo-2024', video_title: 'Product Demo', viewer_user_id: user.id, }} />

The analytics appear in your Mux dashboard without additional configuration.

LinkFAQ

LinkHow can I add video streaming features to my Next.js application?

Install @mux/mux-player-react, create a client component wrapper, and use <MuxPlayer playbackId="..." /> to embed videos. Mux handles encoding, adaptive streaming, and global delivery. Upload videos via the Mux API or use next-video for automatic handling during development.

LinkWhat's the easiest way to embed an adaptive HLS video player in Next.js?

Use Mux Player, which handles HLS internally. Install the package, pass your playback ID, and the component manages quality switching, browser compatibility, and playback optimization automatically. No HLS.js configuration required.

LinkWhat's the easiest way to integrate video streaming into a Vercel-hosted website?

Connect Mux through the Vercel Marketplace. This adds API credentials as environment variables automatically. Then use the Mux API from Server Actions or Route Handlers to create assets and get playback IDs for your frontend.

LinkHow can I use a video API to embed a muted, autoplaying hero video in Next.js?

Use Mux Player with autoPlay="muted", loop, muted, and playsInline props. Hide controls with style={{ '--controls': 'none' }}. Position the player absolutely within a container and overlay your content on top with position: relative and z-index.

LinkHow do I implement video playback in Next.js with an easy-to-use API?

Upload videos to Mux (via API or next-video), get a playback ID, and embed with <MuxPlayer playbackId="..." />. The player handles HLS streaming, captions, accessibility, and analytics out of the box. Server Components fetch metadata; client components render the player.

Arrow RightBack to Articles

No credit card required to start using Mux.