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
- Make your onInit return a no-op cleanup if there is nothing to release: `return () => {};`.
- Return the actual teardown: `return () => { device.destroy(); ctx.unmap(); }`.
- If onInit is async, ensure the resolved value is a function: `return Promise.resolve(() => {})` or `return asyncCleanup;`.
- 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
- Always return a function from onInit, even a no-op `() => {}`.
- Add an eslint `consistent-return` rule on onInit handlers.
- Type onInit return as `() => void` or `Promise<() => void>`.
- Unit-test onInit to assert the resolved value is callable.
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
- No canvas recording was available to open.
- HtmlInCanvas: `width` and `height` must be numbers. Received
- HtmlInCanvas: `width` must be a positive integer. Received:
- HtmlInCanvas: `height` must be a positive integer. Received:
- HtmlInCanvas: `pixelDensity` must be a positive finite numbe
AI-assisted analysis of remotion-dev/remotion@78fe4bb3fd (2026-08-12).
Data as JSON: /api/errors/939cf19999a48b15.
Report an issue: GitHub.