remotion-dev/remotion · error · Error

getVideoMetadata() is only available in the browser.

Error message

getVideoMetadata() is only available in the browser.

What it means

Thrown by getVideoMetadata() when `document` is undefined (line 17). The implementation builds a <video> element via document.createElement and reads loadedmetadata/duration, so it cannot run outside a browser DOM.

Source

Thrown at packages/media-utils/src/get-video-metadata.ts:17

/* eslint-disable @typescript-eslint/no-use-before-define */
import {isRemoteAsset} from './is-remote-asset';
import {onMediaError} from './media-tag-error-handling';
import {pLimit} from './p-limit';
import type {VideoMetadata} from './types';

const cache: {[key: string]: VideoMetadata} = {};

const limit = pLimit(3);

const fn = (src: string): Promise<VideoMetadata> => {
	if (cache[src]) {
		return Promise.resolve(cache[src]);
	}

	if (typeof document === 'undefined') {
		throw new Error('getVideoMetadata() is only available in the browser.');
	}

	const video = document.createElement('video');
	video.src = src;
	return new Promise<VideoMetadata>((resolve, reject) => {
		const onError = () => {
			onMediaError({
				error: video.error!,
				src,
				cleanup,
				reject,
				api: 'getVideoMetadata()',
			});
		};

		const onLoadedMetadata = () => {
			const pixels = video.videoHeight * video.videoWidth;

View on GitHub (pinned to 78fe4bb3fd)

Solutions

  1. Call getVideoMetadata() only from Remotion components or other browser code paths.
  2. For server-side metadata, use Mediabunny's metadata API or ffprobe, which do not require a DOM.
  3. In tests, run under jsdom/happy-dom or use the Remotion browser testbed; do not invoke under plain Node.
  4. Guard with typeof document !== 'undefined' before calling during any isomorphic code path.

Example fix

// before (server call throws)
import {getVideoMetadata} from '@remotion/media-utils';
const meta = await getVideoMetadata('/clip.mp4');

// after (client-side via a component effect)
import {getVideoMetadata} from '@remotion/media-utils';
useEffect(() => {
  if (typeof document === 'undefined') return;
  getVideoMetadata(staticFile('clip.mp4')).then(setMeta);
}, []);
Defensive patterns

Strategy: validation

Validate before calling

import {getVideoMetadata} from '@remotion/media-utils';

async function safeGetVideoMetadata(src: string) {
  if (typeof document === 'undefined') {
    return null; // caller should fall back to ffprobe / Mediabunny metadata
  }
  return getVideoMetadata(src);
}

Type guard

const canUseDomVideo = (): boolean => typeof document !== 'undefined';

Try / catch

try {
  const meta = await getVideoMetadata(src);
} catch (err) {
  if ((err as Error).message.includes('only available in the browser')) {
    // server path: use ffprobe / Mediabunny
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: Calling getVideoMetadata() in SSR, in Node scripts, in Node test runners without a DOM polyfill, or in worker/edge runtimes where document is not defined.

Common situations: Pre-rendering a video gallery server-side; Next.js server component fetching video metadata; CI/build-time asset validation; importing the helper into a build script that runs under Node.

Related errors


AI-assisted analysis of remotion-dev/remotion@78fe4bb3fd (2026-08-12). Data as JSON: /api/errors/b58556e1283ea6e3. Report an issue: GitHub.