remotion-dev/remotion · error

Could not find a root JSX element

Error message

Could not find a root JSX element

What it means

appendElementToRoot is a recast-based codemod helper in @remotion/studio-codemods that appends a JSX element to a component's returned root. It expects the enclosing return statement's argument to be a JSXFragment or JSXElement (the root JSX). If the argument is something else (null, a call expression like createElement(), a conditional, etc.) it throws 'Could not find a root JSX element'.

Source

Thrown at packages/studio-codemods/src/recast-mods.ts:471

}) => {
	folderElement.openingElement.selfClosing = false;
	folderElement.closingElement = folderElement.closingElement ?? {
		type: 'JSXClosingElement',
		name: folderElement.openingElement.name,
	};
	folderElement.children.push(stripParenthesizedExtra(element));
};

const appendElementToRoot = ({
	element,
	returnStatement,
}: {
	element: JSXElement;
	returnStatement: ReturnStatement;
}) => {
	const {argument} = returnStatement;
	if (argument?.type !== 'JSXFragment' && argument?.type !== 'JSXElement') {
		throw new Error('Could not find a root JSX element');
	}

	if (argument.type === 'JSXFragment') {
		(argument.children as JSXFragment['children']).push(
			stripParenthesizedExtra(element),
		);
	} else {
		returnStatement.argument = wrapInJsxFragment([
			argument as unknown as JSXElement,
			element,
		]) as never;
	}
};

const getEnclosingReturnStatement = (path: recast.types.NodePath) => {
	let currentPath: recast.types.NodePath | null = path;
	while (currentPath !== null) {
		if (

View on GitHub (pinned to a6a7485a9a)

Solutions

  1. Rewrite the Root component to return a literal JSX element or fragment: return (<>...</>)
  2. Remove or hoist early non-JSX returns (e.g. `return null`) so the enclosing return statement yields JSX
  3. If the root is wrapped in a conditional, extract the conditional inside a fragment: return (<>{cond ? <A/> : <B/>}</>)
  4. Run the codemod on a file that directly contains the JSX Root rather than a re-export module

Example fix

// before
export const RemotionRoot = () => {
  if (!videos.length) return null;
  return <>{videos.map(...)}</>;
};
// after
export const RemotionRoot = () => {
  return (<>{videos.length ? videos.map(...) : null}</>);
};
Defensive patterns

Strategy: validation

Validate before calling

const returnsJsx = /return\s*\(?\s*<[A-Za-z>]/.test(componentSource);
if (!returnsJsx) throw new Error('Root must return JSX for codemods');

Type guard

const isJsxReturn = (arg: unknown): arg is JSXElement | JSXFragment =>
  arg !== null && typeof arg === 'object' &&
  ['JSXElement', 'JSXFragment'].includes((arg as {type?: string}).type ?? '');

Try / catch

try {
  applyCodemod(file, transformation);
} catch (err) {
  if (err instanceof Error && err.message === 'Could not find a root JSX element') {
    console.warn('Root file does not return JSX directly; skipping codemod');
    return {applied: false};
  }
  throw err;
}

Prevention

When it happens

Trigger: Running a move-composition/move-to-folder codemod against a Root file whose component returns a non-JSX root — e.g. return null, return createComponent(...), a conditional expression, or a return statement whose argument is missing.

Common situations: Root files written with React.createElement instead of JSX; early `return null;` before the JSX return; commented-out or generated Root files without a JSX return; files that re-export the Root from another module.

Related errors


AI-assisted analysis of remotion-dev/remotion@a6a7485a9a (2026-09-02). Data as JSON: /api/errors/2a6a6054a4c6cfe8. Report an issue: GitHub.