How to prepare a video for a next.js website locally
- Step 1Optimise the source with Web Optimize — Drop your raw export (MP4/MOV/MKV/WebM) onto the tool. The fixed recipe gives you a 1280px H.264 CRF 23 + AAC 128k + faststart MP4. One file per run; Free up to 1 GB, Pro 10 GB, Pro + Media 100 GB.
- Step 2Put the output in /public — Save the result to
public/videos/hero-web.mp4. Files in/publicare served from the site root, so it's reachable at/videos/hero-web.mp4. Don't import video through Webpack/Turbopack —/publicis the right home for media. - Step 3Reference it from a <video> element — Use a plain
<video>:<video src="/videos/hero-web.mp4" muted playsinline autoplay loop preload="metadata" />. Next.js has no<Video>component equivalent tonext/image— a standard HTML5<video>is correct here. - Step 4Add a poster from /public — Export a poster frame with thumbnail-extractor, compress it, and put it in
/publictoo:poster="/videos/hero-poster.webp". This is what paints first and keeps your LCP fast — see the Core Web Vitals guide. - Step 5Test in next dev and a production build — Run
next devand confirm progressive playback, thennext build && next start(or deploy a preview) to verify it behaves the same. Because the file is a standard faststart MP4, behaviour is identical across environments. - Step 6Commit the optimised file, not the master — Add the slim MP4 to git (or your asset host). Keep the heavy master out of the repo. If the file is large enough to bloat the repo, host it on a CDN/object store and reference the absolute URL instead of
/public.
Where the optimised video goes in a framework project
The right home for a self-hosted MP4 in common JS frameworks.
| Framework | Put the MP4 in | Reference as |
|---|---|---|
| Next.js (App or Pages) | public/videos/hero-web.mp4 | <video src="/videos/hero-web.mp4"> |
| Vite + React | public/hero-web.mp4 | <video src="/hero-web.mp4"> |
| Create React App | public/hero-web.mp4 | <video src="%PUBLIC_URL%/hero-web.mp4"> |
| Astro | public/hero-web.mp4 | <video src="/hero-web.mp4"> |
| Any static host | alongside the HTML / on a CDN | relative or absolute URL |
Why the file matters in a Next.js build
Framework facts that make the Web Optimize output the right shape.
| Concern | Reality | How Web Optimize helps |
|---|---|---|
| Does next/image optimise video? | No — next/image is for images only | The committed MP4 is the shipped MP4; keep it small (1280px, CRF 23) |
| Build-time encoding step? | None needed | Prep the asset in-browser; commit the result — no CI encode |
Static export (output: 'export')? | /public files are copied verbatim | faststart MP4 works on any static host |
| Progressive playback in dev? | Works if moov is at the front | +faststart applied → instant play in next dev and prod |
Cookbook
From raw export to a working Next.js hero video, locally. JAD runs the FFmpeg equivalent in your browser.
The recipe Web Optimize runs
Produces the canonical framework-ready MP4. Equivalent FFmpeg, but it happens in your browser tab with nothing installed.
ffmpeg -i raw-export.mov \ -vf "scale='min(1280,iw)':-2:flags=lanczos" \ -c:v libx264 -preset medium -crf 23 -pix_fmt yuv420p \ -c:a aac -b:a 128k -movflags +faststart \ hero-web.mp4 → save to public/videos/hero-web.mp4
Next.js <video> component
A standard HTML5 video referencing the file from /public. No special Next component is needed — next/image is images-only.
// app/page.tsx
export default function Hero() {
return (
<video
src="/videos/hero-web.mp4"
poster="/videos/hero-poster.webp"
autoPlay muted loop playsInline
preload="metadata"
width={1280} height={720}
className="hero-video"
/>
);
}Multiple sources for broader compatibility (optional)
If you also produce a WebM with a sibling tool, offer both and let the browser pick. Web Optimize itself only outputs MP4/H.264.
<video poster="/videos/hero-poster.webp"
autoPlay muted loop playsInline preload="metadata">
<source src="/videos/hero-web.webm" type="video/webm" />
<source src="/videos/hero-web.mp4" type="video/mp4" />
</video>
# WebM comes from video-transcoder (codec: vp9, targetFormat: webm)
# /video-tools/video-transcoderKeeping the repo small
Encode locally from a big master (up to 100 GB on Pro + Media), commit only the slim output, and keep the master out of git.
master.mov (8 GB ProRes) ← do NOT commit → Web Optimize (in browser) → hero-web.mp4 (~25 MB) ← commit to public/videos/ .gitignore: /raw-masters/
Verifying it behaves the same in prod
Because the output is a standard faststart MP4, dev and prod behave identically. Throttle the network to confirm progressive playback.
$ next dev → http://localhost:3000 (plays progressively) $ next build && next start → same behaviour DevTools → Network → Slow 3G → reload: video starts after a short buffer (faststart working)
Edge cases and what actually happens
Tried to import the video through Webpack/Turbopack
Use /public insteadImporting a multi-MB video as a module (import vid from './hero.mp4') bloats the bundle and isn't how media should be served. Put the file in /public and reference it by URL (/videos/hero-web.mp4). Web Optimize gives you a self-contained MP4 ready for that.
Expected next/image to shrink the video
Images onlynext/image and Vercel's optimisation pipeline handle images, not video. The MP4 you commit is exactly what ships — there's no automatic resize/transcode on deploy. That's precisely why optimising it first (1280px, CRF 23) matters: it controls both your artifact size and what users download.
Video buffers before playing in next dev
Check moov atomIf a video stutters or waits in dev, its moov atom is likely at the end. Web Optimize always applies +faststart, so its output won't have this problem. If you're seeing it, you probably referenced the raw export instead of the optimised file — point <video src> at the Web Optimize output.
Static export (`output: 'export'`) and the video
SupportedFiles in /public are copied verbatim into the static export and work on any host. A faststart MP4 plays progressively as long as the host honours HTTP range requests. No server-side runtime is needed — exactly what a static Next/Astro export wants.
Large MP4 bloats the git repo / deploy
Host externallyEven an optimised 1080p clip can be tens of MB — large for a git repo and slow for some deploy flows. If it's big, host the MP4 on a CDN/object store and reference the absolute URL instead of /public. Web Optimize still produces the slim file; where you put it is your call.
Need WebM as well as MP4
Use a sibling toolWeb Optimize only outputs MP4/H.264. To also ship WebM/VP9 (smaller for supporting browsers), produce it with video-transcoder (codec vp9, container webm) and add a second <source>. The browser picks the first it can play.
Source over the tier limit during local prep
RejectedFree rejects sources over 1 GB. A raw 4K master may exceed it. Upgrade (Pro 10 GB / Pro + Media 100 GB) or trim with lossless-trimmer first. The optimised file you commit will be far under any limit.
4K master OOMs while encoding in the browser
OOM on mobileEncoding a 4K master can exhaust the WebAssembly heap on mobile. Do asset prep on your desktop dev machine (where you're building the app anyway). The shipped file is small regardless of where you encoded it.
Autoplay not firing in the React component
Attribute issueReact needs camelCase boolean attributes: autoPlay muted loop playsInline. Browser policy requires muted for autoplay. The file is fine (faststart MP4); fix the JSX attributes if autoplay doesn't trigger.
Different resolution needed than 1280px
Use video-transcoderWeb Optimize is fixed at a 1280px width cap and CRF 23. If your design needs 1080p kept, or a specific CRF/codec, use video-transcoder for full control, then drop that output in /public the same way.
Frequently asked questions
What video format should I use for a Next.js site?
H.264 + AAC in an MP4 with faststart, at a web-appropriate resolution. That's the most compatible self-hosted web video format and exactly what Web Optimize produces (1280px H.264 CRF 23, AAC 128k, +faststart). Drop it in /public and reference it from a standard <video>.
Where do I put the video file in a Next.js project?
In /public — e.g. public/videos/hero-web.mp4, served at /videos/hero-web.mp4. Don't import it as a module through Webpack/Turbopack; that bloats the bundle. /public files are served as static assets and copied verbatim, including in a static export.
Does Next.js or Vercel optimise my video automatically?
No. next/image and Vercel's optimisation handle images only — video is served as-is. The MP4 you commit is the MP4 your users download. That's why you optimise it first with Web Optimize (1280px, CRF 23, faststart) to control both deploy size and delivery.
Is there a <Video> component like next/image?
No. There's no first-party Next.js video-optimisation component. Use a plain HTML5 <video> (with autoPlay muted loop playsInline preload="metadata" in JSX). Web Optimize handles the encoding side; the markup is standard.
Do I need FFmpeg installed to prep the video?
No. Web Optimize runs FFmpeg.wasm in your browser — no local FFmpeg install, no npm package, no CI encode step, no third-party API. You prep the asset on your machine and commit the result, keeping your build pipeline clean.
Will the video play the same in next dev and production?
Yes. The output is a standard faststart MP4 with yuv420p H.264 — there are no codec or container differences between environments. It plays progressively in next dev, next start, and any static export, as long as the host supports HTTP range requests.
How do I add a poster image?
Export a frame with thumbnail-extractor, compress it to WebP/AVIF, put it in /public, and set poster="/videos/hero-poster.webp". The poster paints first and keeps your LCP fast — important for Core Web Vitals.
Should I also ship a WebM version?
Optionally. WebM/VP9 is smaller for supporting browsers, but H.264/MP4 alone plays everywhere. Web Optimize only outputs MP4. If you want both, make the WebM with video-transcoder and add a second <source>; list WebM first so capable browsers pick it.
My optimised file is still big for a git repo — what now?
Host the MP4 on a CDN or object store and reference the absolute URL from <video src> instead of /public. Web Optimize gives you the smallest reasonable H.264 file; if it's still large, the asset is just heavy and is better served from a CDN than committed.
Is the video uploaded anywhere during prep?
No. Everything runs in your browser via FFmpeg.wasm. Your source never leaves your machine — useful when the footage is pre-release product or under NDA and shouldn't touch a third-party encoding service.
What if I need 1080p instead of 1280px?
Web Optimize caps width at 1280px and uses CRF 23 with no options. For 1080p, a specific CRF, or a different codec, use video-transcoder for full control, then place that output in /public the same way.
How large a source can I prep locally?
Free up to 1 GB, Pro up to 10 GB, Pro + Media up to 100 GB. Encode large masters on a desktop dev machine (mobile may OOM on 4K), and commit only the slim output. The limit is file size, not duration.
Privacy first
Every JAD Video tool runs entirely in your browser via WebCodecs and FFmpeg (WebAssembly). Your video files never leave your device — verified by zero outbound network requests during processing.