remotion-dev/remotion · error · Error

measureText() can only be called in a browser.

Error message

measureText() can only be called in a browser.

What it means

measureText() in @remotion/layout-utils creates a DOM span and calls getBoundingClientRect / getComputedStyle, so it fundamentally requires a browser. It throws this if `typeof document === 'undefined'`, i.e. when the call happens in Node.js, Bun, or any non-DOM runtime.

Source

Thrown at packages/layout-utils/src/layouts/measure-text.ts:61

} & CSSPropertiesOnWord;

const wordCache = new Map<string, Dimensions>();

const takeMeasurement = ({
	text,
	fontFamily,
	fontSize,
	fontWeight,
	letterSpacing,
	fontVariantNumeric,
	additionalStyles,
	textTransform,
}: Omit<Word, 'fontFamily'> & {fontFamily: string | null}): {
	boundingBox: DOMRect;
	computedFontFamily: string;
} => {
	if (typeof document === 'undefined') {
		throw new Error('measureText() can only be called in a browser.');
	}

	const node = document.createElement('span');

	if (fontFamily) {
		node.style.fontFamily = fontFamily;
	}

	node.style.display = 'inline-block';
	node.style.position = 'absolute';
	node.style.top = `-10000px`;
	node.style.whiteSpace = 'pre';
	node.style.fontSize =
		typeof fontSize === 'string' ? fontSize : `${fontSize}px`;

	if (additionalStyles) {
		for (const key of Object.keys(
			additionalStyles,

View on GitHub (pinned to 78fe4bb3fd)

Solutions

  1. Move the measureText() call into a component body or hook that only executes during Remotion rendering (browser context).
  2. Configure your test runner with a DOM environment (jsdom/happy-dom) if the function must run in tests.
  3. Avoid importing layout-utils into server-only code paths.
  4. Gate the call: `if (typeof document !== 'undefined') { measureText(...) }` when shared between server and client.

Example fix

// before
// module scope, runs in Node
const widths = words.map((w) => measureText({text: w, fontFamily: 'Inter', fontSize: 16}));
// after
import {useEffect} from 'react';
function MyComp({words}) {
  const [widths, setWidths] = useState<Record<string, Dimensions>>({});
  useEffect(() => {
    const next: Record<string, Dimensions> = {};
    for (const w of words) next[w] = measureText({text: w, fontFamily: 'Inter', fontSize: 16});
    setWidths(next);
  }, [words]);
  return null;
}
Defensive patterns

Strategy: type-guard

Validate before calling

const canMeasure = typeof document !== 'undefined';
if (!canMeasure) {
  // skip measurement or precompute from a fixture
}

Type guard

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

Try / catch

try {
  const dims = measureText({text, fontFamily: 'Inter', fontSize: 16});
} catch (e) {
  if ((e as Error).message.includes('browser')) {
    // SSR/test path: return fallback dims
  } else throw e;
}

Prevention

When it happens

Trigger: Calling measureText() during SSR, in a Node test (jest/vitest without jsdom), in a server component, or in a top-level module-evaluation context that runs outside the Remotion browser render.

Common situations: Importing a layout utility that calls measureText() into a Node-side script (e.g. to precompute widths); running component tests without a DOM environment; calling measureText() at module scope rather than inside a component body that only runs in the browser.

Related errors


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