How to optimize an mp4 for a website so it loads faster
- Step 1Drop your MP4 (or MOV/MKV/WebM) onto the tool — Web Optimize takes one file at a time (it does not batch). The source can be MP4, MOV, MKV, or WebM — FFmpeg.wasm reads the container and re-muxes to MP4 regardless. Free tier allows files up to 1 GB; Pro up to 10 GB; Pro + Media up to 100 GB.
- Step 2Confirm the fixed recipe — there is nothing to configure — The options panel shows a single line: "Scales to 720p, encodes H.264 + AAC, sets +faststart for instant playback. No options needed." In practice the width cap is 1280px (
min(1280,iw)), not a hard 720, and the source is only downscaled, never upscaled. If you need a different resolution or CRF, use video-transcoder instead. - Step 3Run the optimisation — FFmpeg.wasm decodes the source, applies the Lanczos scale filter, encodes H.264 at CRF 23 (preset medium), re-encodes audio to AAC 128k, and writes the moov atom to the front. The progress bar tracks
time=lines from FFmpeg as it works through the timeline. - Step 4Verify the moov atom is at the front — Run
ffprobe -v trace yourfile.mp4 2>&1 | head -40(or any MP4 atom inspector) and confirm themoovbox appears beforemdat. JAD's output always has it at the front because the recipe hard-codes+faststart. - Step 5Drop the file into your CMS or static host — The result is a standard progressive-download MP4 — works as a
<video>src, in WordPress/Ghost media libraries, and on any static host (Netlify, Vercel, S3 + CloudFront). No HLS manifest is produced; this is single-file progressive delivery, not adaptive streaming. - Step 6Spot-check size and playback — Compare input vs output bytes (shown after the run) and scrub the result in a browser. If it is still too large for your bandwidth budget, follow up with video-bitrate-set for an exact target, or read the bandwidth-cost guide.
The exact Web Optimize recipe (hard-coded, no UI controls)
Every value below is fixed in the processor — there is no panel to change them. Verified against runWebOptimize in lib/video/video-processor.ts.
| Stage | FFmpeg flag | Effect | Adjustable? |
|---|---|---|---|
| Scale | -vf scale='min(1280,iw)':-2:flags=lanczos | Caps width at 1280px, keeps aspect (-2 = even auto-height), Lanczos resampling. Downscale-only — a narrower source is left as-is | No — use video-transcoder for other sizes |
| Video codec | -c:v libx264 -preset medium -crf 23 | Software H.264 at constant-quality CRF 23 (lower = better/larger). preset medium balances speed vs efficiency | No — use video-transcoder for other CRF/codec |
| Pixel format | -pix_fmt yuv420p | 8-bit 4:2:0 chroma — the only format every browser and Safari hardware-decodes | No |
| Audio codec | -c:a aac -b:a 128k | Always re-encodes audio to 128 kbps AAC — audio is NOT stream-copied | No |
| Web flag | -movflags +faststart | Relocates the moov atom to the file start for progressive download | No (always on) |
| Container | output .mp4 | Output is always MP4 regardless of input container | No |
What the source looks like before vs after
Typical transforms. Exact output bytes depend on motion/grain in the source; CRF means size is content-dependent, not fixed.
| Source | After Web Optimize | Why |
|---|---|---|
| 1920×1080 H.264, moov at end, 60 MB | 1280×720 H.264 CRF 23, moov at front, ~12–20 MB | Width capped to 1280 + CRF 23 re-encode + faststart |
| 1280×720 source | Stays 1280×720, re-encoded CRF 23 + faststart | min(1280,iw) leaves width unchanged; never upscaled |
| 640×360 source | Stays 640×360, re-encoded CRF 23 + faststart | Downscale-only — small sources keep their resolution |
| 4K (3840×2160) source | 1280-wide, re-encoded CRF 23 + faststart | Aggressive downscale; the heavy decode may stress mobile heaps |
| MOV/MKV/WebM source | Always .mp4 output | Re-muxed to MP4 with H.264 + AAC |
Cookbook
Concrete before/after for the website-speed use case. Commands shown are the FFmpeg equivalent of what JAD runs in your browser.
Hero MP4 that buffered before playing now plays instantly
A phone-shot 1080p hero clip dropped into a homepage <video> made visitors stare at a blank box until the whole file downloaded — because the moov atom was at the end. Web Optimize re-encodes and front-loads the moov.
Before:
hero.mp4 · 1920x1080 · 58 MB · moov atom at END
Browser: downloads all 58 MB, THEN paints frame 1
JAD Web Optimize (in-browser, equivalent FFmpeg):
ffmpeg -i hero.mp4 \
-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
After:
hero-web.mp4 · 1280x720 · ~14 MB · moov atom at FRONT
Browser: starts playing after a ~1s bufferConfirming the optimisation actually took
Two checks tell you Web Optimize did its job: the moov box moved to the front, and the width is at most 1280. Use ffprobe to verify both.
# moov at front? (should appear before mdat) ffprobe -v trace hero-web.mp4 2>&1 | grep -E "type:'(moov|mdat)'" | head -2 # width capped at 1280? ffprobe -v error -select_streams v:0 \ -show_entries stream=width,height -of csv=p=0 hero-web.mp4 → 1280,720
A 720p source is re-encoded but not resized
Because the scale filter is min(1280,iw), a source already at or below 1280px wide keeps its dimensions. Web Optimize still re-encodes at CRF 23 and adds faststart — useful purely to fix a back-loaded moov atom.
Input: clip.mp4 · 1280x720 · moov at end · 22 MB Output: clip-web.mp4 · 1280x720 (unchanged) · moov at front · ~10 MB Resolution preserved; the win here is faststart + CRF 23 size cut.
When you need a different size — hand off to video-transcoder
Web Optimize has no resolution or CRF control. If you want, say, 1080p kept at a tighter CRF, the right tool is the transcoder, which exposes codec + CRF + container.
Want 1080p @ CRF 20 instead of 1280 @ CRF 23? Use video-transcoder: /video-tools/video-transcoder targetFormat: mp4 · codec: h264 · crf: 20 Web Optimize is the zero-config path; transcoder is the full-control path.
Optimising a screen recording for a docs page
Screen-capture MP4s are often huge for what they show (low motion, text). CRF 23 handles low-motion content very efficiently, so the size drop is dramatic and text stays crisp at 1280px.
Input: screencast.mov · 2560x1440 · 140 MB Output: screencast-web.mp4 · 1280x720 · ~8 MB Low-motion text content compresses hard at CRF 23 — typically the biggest percentage savings you'll see.
Edge cases and what actually happens
Source is already 720p or smaller — no resize happens
By designThe scale filter is min(1280,iw), so any source at or below 1280px wide keeps its own width. The tool still re-encodes at CRF 23 and applies faststart, but it will never upscale to fill 1280px. If you wanted a higher output resolution than the source, that is not possible — Web Optimize only ever holds or reduces resolution.
Output is larger than you expected on a grainy/high-motion clip
ExpectedCRF is constant-quality, not constant-size: film grain, confetti, fast pans, and noise all force more bits to hold quality at CRF 23. There is no size target here. If you need the file under a specific byte budget, use video-bitrate-set or a size-targeted compressor like discord-compressor.
Audio is re-encoded even though you wanted it untouched
By designWeb Optimize always runs -c:a aac -b:a 128k — it does not stream-copy audio. A pristine multichannel or high-bitrate source track is downmixed/re-encoded to 128k stereo AAC. This is intentional for small web files, but if you need the original audio preserved bit-for-bit, this is the wrong tool — use lossless-trimmer (stream-copy) for a no-re-encode export.
Large 4K source on a phone runs out of memory
OOM on mobileFFmpeg.wasm runs in the browser's WebAssembly heap (~512 MB practical ceiling on Android Chrome, far less on iOS Safari). Decoding 4K frames plus running libx264 can exhaust it and abort with Aborted(OOM). Trim or downscale the source first on desktop, or run the optimisation on a desktop browser where the heap is larger.
Input container is MOV/MKV/WebM, not MP4
SupportedAny container FFmpeg.wasm can demux works — MOV, MKV, WebM, AVI. The output is always re-muxed to .mp4 with H.264 video and AAC audio, so a WebM/VP9 source comes out as an MP4/H.264 file. There is no option to keep the source container.
You hoped for HLS / adaptive streaming output
Not supportedWeb Optimize produces a single progressive-download .mp4, not an .m3u8 HLS manifest or DASH segments. Progressive download (faststart) lets playback begin before the full file loads, but it is one file at one bitrate — there is no adaptive ladder. For true adaptive streaming you'd need a separate packaging step outside JAD.
Trying to optimise several files at once
Single file onlyThe tool accepts one file per run (acceptsMultiple: false). There is no batch queue or drag-reorder. Run videos one at a time. Higher tiers raise the per-file size ceiling (Pro 10 GB, Pro + Media 100 GB) but Web Optimize still processes a single file each pass.
10-bit / HDR source flattened to 8-bit SDR
ExpectedThe recipe forces -pix_fmt yuv420p (8-bit 4:2:0). A 10-bit HDR source is converted to 8-bit SDR, which is correct for broad web compatibility but discards HDR metadata and the extra bit depth. If you need to preserve 10-bit, use video-transcoder with a codec/profile that keeps it.
File exceeds your tier's size limit
RejectedFree rejects files over 1 GB, Pro over 10 GB, Pro + Media over 100 GB, with a message naming your tier and the limit. The cap is on file size, not duration — there is no minutes limit. Upgrade, or trim the clip with lossless-trimmer before optimising.
Output won't autoplay despite faststart
Check page markupFaststart fixes the download-before-play problem, but browser autoplay policies still require muted + playsinline (and often autoplay) attributes on the <video> tag. If your hero won't autoplay, the issue is the markup/policy, not the file — confirm the moov atom is at the front (it is) and add the required attributes.
Frequently asked questions
What exactly does Web Optimize change?
Four things, all fixed: it caps width at 1280px with Lanczos scaling (scale='min(1280,iw)':-2:flags=lanczos), re-encodes video with libx264 at preset medium, CRF 23, yuv420p, re-encodes audio to AAC 128k, and sets -movflags +faststart so the moov atom sits at the front. Output is always .mp4. There are no options to change any of this.
Does it really scale to 720p?
The UI label says "720p" loosely, but the actual filter caps width at 1280px. A 1920×1080 source becomes 1280×720 (16:9), which is 720p — but a 4:3 or vertical source will land at 1280px wide with a different height. And anything already ≤1280px wide keeps its width unchanged. It is a width cap, not a hard 720p target.
Will my video be uploaded anywhere?
No. Web Optimize runs entirely in your browser via FFmpeg.wasm. The file is decoded, re-encoded, and re-muxed in the tab — nothing is sent to a server or CDN. Only an anonymous "file processed" counter (no content) is recorded for signed-in dashboard stats, and you can opt out.
Why is the moov atom placement such a big deal?
An MP4's moov atom holds the index a player needs to decode the file. FFmpeg writes it at the end by default. For a web <video> doing progressive download, a back-of-file moov means the browser must download the whole file before it can start. +faststart rewrites the file with the moov at the front so playback begins after a small buffer.
Can I pick a different CRF, codec, or resolution?
Not in this tool — it is intentionally zero-config. For a specific CRF, codec (H.265/AV1/VP9), container, or resolution, use video-transcoder. For an exact bitrate, use video-bitrate-set. For an exact pixel size, use video-resizer.
Does it preserve my original audio track?
No. Audio is always re-encoded to 128 kbps stereo AAC — it is not stream-copied. If your source has a higher-bitrate or multichannel track you want kept intact, Web Optimize is the wrong tool. Use a stream-copy export instead.
What input formats can I feed it?
Anything FFmpeg.wasm can demux — MP4, MOV, MKV, WebM, AVI, and more. Regardless of input, the output is a re-muxed .mp4 with H.264 video and AAC audio. There is no option to keep the source container.
How large a file can I optimise?
Free tier up to 1 GB, Pro up to 10 GB, Pro + Media up to 100 GB. The limit is on file size only — there is no duration/minutes cap. On mobile browsers, very large 4K sources may still hit the WebAssembly memory ceiling and fail with an OOM error even under the size limit.
Will the output play on Safari / iOS?
Yes — that's exactly what yuv420p + H.264 + AAC + MP4 targets. yuv420p is the only chroma format Apple hardware decoders accept, so forcing it guarantees iOS Safari and macOS playback. A non-yuv420p source that played in Chrome but failed on iOS will play after Web Optimize.
Does it generate HLS or adaptive streaming?
No. The output is a single progressive-download MP4 at one bitrate — no .m3u8 manifest, no DASH, no bitrate ladder. Faststart enables play-before-full-download, but that is not the same as adaptive streaming. Packaging for HLS/DASH is a separate workflow outside JAD.
Why did my file barely shrink?
CRF is constant-quality, so high-motion or grainy footage needs more bits to hold quality at CRF 23 — the size drop can be modest. Sources already near 1280px wide and lightly compressed also have less headroom. If you need a guaranteed smaller file, target a bitrate with video-bitrate-set.
Is this the same as HandBrake's web preset?
Conceptually yes — H.264 + faststart for web delivery — but it runs in your browser with nothing to install. The no-HandBrake guide walks through the equivalence. The trade-off is that HandBrake lets you tune everything; Web Optimize is deliberately one fixed recipe.
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.