Common problem for motion designers and video editors starting with websites: “I made a beautiful animation in Adobe After Effects, rendered to .mov (in best quality!), uploaded to WordPress, and nothing. Black screen, error, or the file weighs 500 MB and loads for an hour.”
The file is not broken. It was made for an edit suite, and a browser is not an edit suite.
The problem with .mov files
.mov is a container, not a codec. What makes it unusable on the web is usually what is inside it: ProRes, or uncompressed frames.
The sizes are not a matter of opinion. Apple publishes them. At 1920x1080 and 29.97 fps, the ProRes white paper gives target data rates of 45 Mbps for 422 Proxy, 102 Mbps for 422 LT, 147 Mbps for 422, 220 Mbps for 422 HQ, 330 Mbps for 4444 and 495 Mbps for 4444 XQ. The appendix in the same document restates those as gigabytes per hour: 422 HQ is 99 GB per hour, which is about 1.65 GB for a single minute of footage. A twenty second hero loop straight out of After Effects can therefore be larger than the entire rest of the site.
Two things then go wrong inside WordPress, in this order.
The upload may not finish at all. wp_max_upload_size() reads upload_max_filesize and post_max_size from PHP and returns the smaller of the two. On shared hosting those are frequently left at modest defaults, and a multi hundred megabyte POST dies before the media library ever sees it.
If it does finish, it still will not play. This is the part nobody explains. WordPress accepts the file because wp_get_mime_types() contains 'mov|qt' => 'video/quicktime'. But wp_get_video_extensions(), the function that decides what core considers a playable video source, returns only mp4, m4v, webm, ogv and flv. MOV is not on that list. So the attachment sits in the library looking like a video and behaving like a download.
Even if you force the markup by hand, the browser will not save you. MDN’s container guide records no QuickTime playback in current Chrome, Edge, Firefox or Safari, and notes that the QuickTime framework itself has been gone from macOS since 10.15 Catalina. A ProRes bitstream has no decoder in any shipping browser.
HTML5 standard: MP4 with H.264 and WebM
Two formats cover the whole web, and the choice between them is less dramatic than it was when this article was first written.
1. MP4 with H.264
The safe default. caniuse puts H.264 in MP4 at roughly 97 percent global support, going back to Chrome 4, Safari 3.2 and IE 9. If you ship one file, ship this one.
Two settings decide whether your export actually works, and both are invisible in a preview player.
Chroma subsampling. ProRes 422 is, as the name says, 4:2:2, and the 422 family is 10 bit. Hand that to libx264 without instruction and it will happily encode a High 4:2:2 profile stream that no browser can decode. Force 8 bit 4:2:0:
ffmpeg -i source.mov -c:v libx264 -crf 23 -preset slow \
-pix_fmt yuv420p -c:a aac -b:a 128k \
-movflags +faststart web.mp4Where the index lives. FFmpeg’s muxer documentation describes -movflags +faststart as a second pass that runs “moving the index (moov atom) to the beginning of the file”, and explicitly notes it is not enabled by default. Without it the browser cannot begin playback until the whole file has arrived, which is exactly the “loads for an hour” symptom. Verify rather than assume, because the flag is silent when it is missing:
grep -abo -m2 -e moov -e mdat web.mp4That prints the byte offset of the first moov and the first mdat. You want the moov offset to be the smaller one.
If you are exporting from Adobe Media Encoder rather than the command line, pick H.264 as the format, not QuickTime, and then run the two checks above on the result. The format list is the only thing that matters; preset names move between releases.
2. WebM with VP9, and AV1
Google’s own VP9 page claims the codec “can reduce video bit rates by as much as 50% compared with other known codecs”. Treat that as a ceiling reached on hard content, not a number you will hit on every clip, but the saving on a flat motion graphic is real.
The stale advice on this topic is that WebM is a Chrome and Firefox format. It is not, and has not been for several years. caniuse records full WebM support in Safari on macOS from version 16 and in Safari on iOS from 17.4. That puts global support at 96.25 percent, under a point behind H.264 in MP4.
ffmpeg -i source.mov -c:v libvpx-vp9 -crf 33 -b:v 0 -row-mt 1 \
-pix_fmt yuv420p -c:a libopus -b:a 96k web.webmAV1 sits one generation further on. caniuse gives it 79.26 percent full support, rising to 94.28 percent only if you count partial, and Safari is the partial one because playback there depends on hardware decode. That makes AV1 a first <source>, never the only one.
Transparency is the one place where no single file wins. VP9 in WebM carries an alpha channel if you encode to yuva420p, and Chromium and Firefox render it. Safari does not. Apple’s answer is HEVC with alpha, which it documents in its HEVC Video with Alpha Interoperability Profile. If an After Effects composition needs a real alpha channel on the web, you are shipping two files and a solid colour fallback.
HandBrake covers all of this in a GUI if you prefer one: its feature list names AV1, H.265, H.264, VP8 and VP9 as encoders, with MP4, MKV and WebM as containers.
The video tag strategy in HTML5
If you are self hosting, for instance as a hero background, list sources best first. The browser takes the first one it can decode and never looks at the rest.
<video autoplay loop muted playsinline preload="none"
poster="/media/hero-poster.avif">
<source src="/media/hero.webm" type="video/webm; codecs=vp9">
<source src="/media/hero.mp4" type="video/mp4; codecs=avc1.42E01E">
Your browser doesn't support video.
</video>The attributes are doing specific jobs:
mutedplusplaysinline: mobile browsers refuse autoplay without both. Withoutplaysinline, iOS takes the video fullscreen the moment it starts, which destroys a background loop.preload="none": nothing is fetched until playback is requested, so the poster carries the first paint. Usemetadataif you need duration up front, andautoalmost never.poster: an AVIF or WebP still is a fraction of the video and is what the visitor actually sees during the first seconds.- the
codecs=part of thetypestring: it lets the browser reject a source without downloading a byte of it.
In the block editor you do not have to write this by hand. The core Video block carries autoplay, controls, loop, muted, playsInline, poster, preload, src and tracks as attributes, and playsInline is exposed in the sidebar as the “Play inline” toggle. The Cover block has no such attribute: its block.json exposes no playsInline key, and its save output hardcodes playsInline on the background video, so a Cover video loop is always inline and there is no toggle to check.
Or maybe YouTube or Vimeo
Self hosting gives you one file at one bitrate delivered by progressive HTTP download. There is no adaptive ladder. WordPress builds resized renditions for images at upload time and does nothing equivalent for video, because core ships no transcoder. A visitor on a weak mobile connection gets your 1080p file or gets nothing, and stalls while it buffers.
So the rule is about what the video is, not how big it is. Content with sound that a visitor came to watch, an interview, a tutorial, a case study walkthrough, belongs on YouTube or Vimeo, where the platform encodes a ladder of renditions and switches between them. A silent decorative loop behind a headline belongs on your own server, because an embed drags in a third party player and its cookies for something that is closer to a moving background image.
Bandwidth is the second half of that decision. A 10 MB loop on a page that gets fifty thousand views a month is half a terabyte of egress, and on entry level hosting that is a conversation with your provider.
Workflow summary
- Edit and master in After Effects or Premiere against
.movand ProRes. That part was never wrong. - Export for the web as H.264 in MP4, forcing
-pix_fmt yuv420pand-movflags +faststart. - Confirm the export: check that
moovsits at a lower byte offset thanmdat, and check withffprobethat the pixel format isyuv420p, notyuv422p10le. - Add a VP9 WebM copy and list it before the MP4 in the
<video>element. Add AV1 above both if you are chasing the last few hundred kilobytes. - Give the element a poster image and
preload="none", and keepmutedandplaysinlineon any autoplaying loop. - If the clip has sound and someone is meant to sit and watch it, put it on YouTube or Vimeo instead and embed it.
If this sits inside a build we maintain, we set the encode step up once in the deploy pipeline so nobody has to remember the flags. That is part of our WordPress development services.







