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
- Keep only the outermost <HtmlInCanvas> and merge the visual treatments into its single effects array (e.g. effects={[blur(5), outline({width: 3})]})
- Move the inner <HtmlInCanvas> out of the parent's children so the two become siblings, not ancestor/descendant
- 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
- Keep one <HtmlInCanvas> per subtree and merge visual treatments into its effects array instead of nesting snapshots
- Audit reusable components for internal <HtmlInCanvas> usage before embedding them in an outer <HtmlInCanvas>
- Treat <HtmlInCanvas> like an effect boundary, not a layout wrapper - apply effects to plain content
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
- <Composition> mounted inside another composition. See https:
- <Clipper> has been removed as of Remotion v4.0.228. The nati
- You must pass either 'component' or 'lazyComponent'
- usePixelDensity() was called outside of a Remotion context.
- No video config found. Likely reasons:- You are probably cal
AI-assisted analysis of remotion-dev/remotion@10db9de073 (2026-08-22).
Data as JSON: /api/errors/a40b32957231662a.
Report an issue: GitHub.