remotion-dev/remotion · error · Error

HtmlInCanvas: when `onInit` is provided, it must return a cl

Error message

HtmlInCanvas: when `onInit` is provided, it must return a cleanup function, or a Promise that resolves to one.

What it means

Thrown inside the onPaint callback of <HtmlInCanvas> when the user-provided `onInit` resolves to a value that is not a function. The contract for onInit is to return a cleanup callback (or a Promise of one) so that Remotion can release paint-target resources (WebGPU device, buffers, textures) when the component unmounts or the canvas remounts. A missing cleanup leaks GPU/canvas state.

Source

Thrown at packages/core/src/HtmlInCanvas.tsx:527

							throw error;
						}

						initializedRef.current = true;
						try {
							if (paintTarget instanceof HTMLCanvasElement) {
								throw new Error(
									'HtmlInCanvas: onInit requires an OffscreenCanvas paint target',
								);
							}

							const cleanup = await currentOnInit({
								canvas: paintTarget,
								element,
								elementImage: initImage,
								pixelDensity: resolvedPixelDensity,
							});
							if (typeof cleanup !== 'function') {
								throw new Error(
									'HtmlInCanvas: when `onInit` is provided, it must return a cleanup function, or a Promise that resolves to one.',
								);
							}

							if (unmountedRef.current) {
								cleanup();
							} else {
								onInitCleanupRef.current = cleanup;
							}
						} finally {
							initImage.close();
						}
					}
				}

				let elImage: ElementImage;
				try {
					elImage = placeholderCanvas.captureElementImage(element);

View on GitHub (pinned to 78fe4bb3fd)

Solutions

  1. Make your onInit return a no-op cleanup if there is nothing to release: `return () => {};`.
  2. Return the actual teardown: `return () => { device.destroy(); ctx.unmap(); }`.
  3. If onInit is async, ensure the resolved value is a function: `return Promise.resolve(() => {})` or `return asyncCleanup;`.
  4. Lint your onInit with an explicit `return` statement (eslint: consistent-return).

Example fix

// before
const onInit = async ({canvas}) => {
  const device = await adapter.requestDevice();
  // no return
};
// after
const onInit = async ({canvas}) => {
  const device = await adapter.requestDevice();
  return () => device.destroy();
};
Defensive patterns

Strategy: validation

Validate before calling

const result = await onInit(params);
if (typeof result !== 'function') {
  throw new TypeError('onInit must return a cleanup function');
}
return result;

Type guard

const isCleanup = (v: unknown): v is () => void => typeof v === 'function';

Try / catch

try {
  const cleanup = await onInit(params);
  if (typeof cleanup !== 'function') {
    throw new Error('onInit did not return a cleanup');
  }
  // store cleanup
} catch (err) {
  // report a user-facing error about onInit contract
  throw err;
}

Prevention

When it happens

Trigger: Implementing `onInit` that returns undefined, null, a non-function value, or forgets the `return` statement; returning a Promise that resolves to a non-function; arrow functions that implicitly return undefined.

Common situations: First-time onInit authors who treat it like onPaint (which is void); async init that sets up a WebGPU device but never returns a destroy callback; refactoring onInit and dropping the return line.

Related errors


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