remotion-dev/remotion · error · Error

You must pass either 'component' or 'lazyComponent'

Error message

You must pass either 'component' or 'lazyComponent'

What it means

Thrown by useLazyComponent when the compProps object contains neither a `component` nor a defined `lazyComponent` key. Remotion's <Composition>, <Player>, and <Still> require exactly one of these props so they know what to render; reaching this branch means the discriminated union was violated at runtime (TypeScript normally prevents it). It is a hard failure that stops the composition from mounting.

Source

Thrown at packages/core/src/use-lazy-component.ts:91

		if (
			'lazyComponent' in compProps &&
			typeof compProps.lazyComponent !== 'undefined'
		) {
			if (typeof compProps.lazyComponent === 'undefined') {
				throw new Error(
					`A value of \`undefined\` was passed to the \`lazyComponent\` prop. Check the value you are passing to the <${componentName}/> component.`,
				);
			}

			return React.lazy(
				compProps.lazyComponent as () => Promise<{
					default: ComponentType<Props>;
				}>,
			);
		}

		throw new Error("You must pass either 'component' or 'lazyComponent'");

		// Very important to leave the dependencies as they are, or instead
		// the player will remount on every frame.
		// For the 'component' case, we intentionally do NOT depend on
		// compProps.component — the stable wrapper reads from componentRef instead.

		// @ts-expect-error
		// eslint-disable-next-line react-hooks/exhaustive-deps
	}, [compProps.lazyComponent]);
	return lazy;
};

View on GitHub (pinned to 78fe4bb3fd)

Solutions

  1. Pass either `component={MyComponent}` or `lazyComponent={React.lazy(() => import('./MyComponent'))}` to the <Composition>/<Player>/<Still>.
  2. If building props dynamically, default to a real component: `const comp = entry.component ?? DefaultComp; <Composition component={comp} .../>`.
  3. Search the codebase for the composition id named in the surrounding error trace and confirm its registration object has a component key.
  4. Add a runtime assert in your registration helper: `if (!entry.component && !entry.lazyComponent) throw new Error('missing component for ' + entry.id);`.

Example fix

// before
<Composition id="x" durationInFrames={120} fps={30} width={1920} height={1080} {...maybeEmptyProps} />

// after
<Composition id="x" durationInFrames={120} fps={30} width={1920} height={1080} component={MyVideo} />
Defensive patterns

Strategy: type-guard

Validate before calling

const hasComponent = (p) => Boolean(p && ('component' in p && p.component) || ('lazyComponent' in p && typeof p.lazyComponent === 'function'));
if (!hasComponent(props)) throw new Error('Composition needs component or lazyComponent');

Type guard

function hasRenderable(p: object): p is { component: unknown } | { lazyComponent: () => Promise<unknown> } {
  return ('component' in p && p.component != null) || ('lazyComponent' in p && typeof (p as any).lazyComponent === 'function');
}

Prevention

When it happens

Trigger: Calling <Composition>/<Still>/<Player> while spreading a props object whose `component`/`lazyComponent` keys are both absent or both explicitly undefined, or constructing compProps dynamically (e.g. from a lookup that returned nothing). Also reachable from JS consumers that bypass the TS union check.

Common situations: Registering compositions from a config array/map where some entries forgot the component field; conditionally building props and the condition evaluated to neither branch; migrating from `component` to `lazyComponent` and leaving both unset during refactor; dynamic registration loops in entry files.

Related errors


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