remotion-dev/remotion · error · Error

<HtmlInCanvas> components cannot be nested. Chrome does not

Error message

<HtmlInCanvas> components cannot be nested. Chrome does not reliably render nested HTML-in-canvas subtrees. Consider merging the effects into one <HtmlInCanvas> if you can.

What it means

Remotion's <HtmlInCanvas> rasterizes an HTML subtree into a <canvas> snapshot. Because Chrome cannot reliably draw an HTML-in-canvas subtree that itself contains another canvas painted from HTML, the component forbids nesting: an internal React context (HtmlInCanvasAncestorContext) is set by every <HtmlInCanvas>, and any descendant <HtmlInCanvas> sees it and throws synchronously during render. The error surfaces in Remotion Studio and at the start of a render.

Source

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

		{
			width,
			height,
			effects,
			children,
			onPaint,
			onInit,
			pixelDensity,
			controls,
			style,
		},
		ref,
	) => {
		const isInsideAncestorHtmlInCanvas = useContext(
			HtmlInCanvasAncestorContext,
		);
		assertHtmlInCanvasDimensions(width, height);
		if (isInsideAncestorHtmlInCanvas) {
			throw new Error(
				'<HtmlInCanvas> components cannot be nested. Chrome does not reliably render nested HTML-in-canvas subtrees. Consider merging the effects into one <HtmlInCanvas> if you can.',
			);
		}

		const resolvedPixelDensity = resolveHtmlInCanvasPixelDensity(pixelDensity);
		const canvasWidth = Math.ceil(width * resolvedPixelDensity);
		const canvasHeight = Math.ceil(height * resolvedPixelDensity);
		const {delayRender, continueRender, cancelRender} = useDelayRender();
		const {isClientSideRendering, isRendering} = useRemotionEnvironment();
		const canRetryMissingPaintRecord = !isRendering || isClientSideRendering;
		const usesDirectLayoutCanvas =
			onPaint === undefined && onInit === undefined;

		if (!isHtmlInCanvasSupported()) {
			cancelRender(new Error(HTML_IN_CANVAS_UNSUPPORTED_MESSAGE));
		}

		const canvas2dRef = useRef<HTMLCanvasElement | null>(null);

View on GitHub (pinned to 10db9de073)

Solutions

  1. Keep only the outermost <HtmlInCanvas> and merge the visual treatments into its single effects array (e.g. effects={[blur(5), outline({width: 3})]})
  2. Move the inner <HtmlInCanvas> out of the parent's children so the two become siblings, not ancestor/descendant
  3. If the outer <HtmlInCanvas> existed only to apply an effect, remove it and apply the effect directly to the inner <HtmlInCanvas> or a plain <Sequence>

Example fix

// before - inner component also renders <HtmlInCanvas>
<HtmlInCanvas width={1920} height={1080} effects={[blur(5)]}>
  <LowerThirdWithOwnHtmlInCanvas />
</HtmlInCanvas>

// after - one <HtmlInCanvas>, effects merged
<HtmlInCanvas
  width={1920}
  height={1080}
  effects={[blur(5), lowerThirdOutline]}
>
  <LowerThirdContent />
</HtmlInCanvas>
Defensive patterns

Strategy: validation

Validate before calling

// Dev-time check: ensure a candidate subtree does not itself contain <HtmlInCanvas>
import type {ReactNode} from 'react';

const subtreeContainsHtmlInCanvas = (node: ReactNode): boolean => {
  if (Array.isArray(node)) return node.some(subtreeContainsHtmlInCanvas);
  if (!node || typeof node !== 'object') return false;
  const el = node as {
    type?: {name?: string; displayName?: string};
    props?: {children?: ReactNode};
  };
  const name = el.type?.displayName ?? el.type?.name;
  if (name === 'HtmlInCanvas') return true;
  return subtreeContainsHtmlInCanvas(el.props?.children);
};

// before rendering: if (subtreeContainsHtmlInCanvas(children)) -> do not wrap in <HtmlInCanvas>

Try / catch

// Studio preview only: catch the render-time throw with an ErrorBoundary
// (during `remotion render` this error is fatal by design)
class HtmlInCanvasBoundary extends React.Component<
  {children: ReactNode; fallback: ReactNode},
  {hasError: boolean}
> {
  state = {hasError: false};
  static getDerivedStateFromError() {
    return {hasError: true};
  }
  render() {
    return this.state.hasError ? this.props.fallback : this.props.children;
  }
}

Prevention

When it happens

Trigger: Rendering JSX where a <HtmlInCanvas> appears anywhere in the children tree of another <HtmlInCanvas> - directly or indirectly through a child component/composition that itself renders <HtmlInCanvas>. The check runs on mount of the inner component (HtmlInCanvasContent, packages/core/src/HtmlInCanvas.tsx:401).

Common situations: Wrapping a whole scene in <HtmlInCanvas> to apply an effect while a reusable child (e.g. a lower-third that already uses <HtmlInCanvas> for its own effect) is placed inside; composing shared libraries that each snapshot their HTML; refactoring an existing <HtmlInCanvas> composition into another one during effect work.

Related errors


AI-assisted analysis of remotion-dev/remotion@10db9de073 (2026-08-22). Data as JSON: /api/errors/a40b32957231662a. Report an issue: GitHub.