remotion-dev/remotion · error

Could not find a root JSX element

Error message

Could not find a root JSX element

What it means

Thrown by getRegistrationRoot in packages/studio-codemods/src/folder-source-edits.ts:143 when walking up the AST from a JSX element finds no `return <JSXElement|JSXFragment>` statement. Remotion Studio codemods assume compositions/folders live inside a JSX tree returned from a Root component; when the moving element's enclosing function has no returned JSX root, the codemod cannot know where to re-insert the element and aborts.

Solutions

  1. Restructure the Root file so the compositions/folders are statically written inside a single `return (<Root>...</Root>)` JSX expression
  2. Move the target composition element out of any helper function or variable into the main returned JSX tree
  3. If the file must build JSX dynamically, perform the move manually in the editor instead of via the Studio codemod

Example fix

// before
const tree = <Root>
  <Composition id="MyComp" ... />
</Root>;
export const RemotionRoot = () => tree;

// after
export const RemotionRoot = () => {
  return (
    <Root>
      <Composition id="MyComp" ... />
    </Root>
  );
};
Defensive patterns

Strategy: validation

Validate before calling

const hasJsxReturnRoot = (source: string) => /return\s*\(?\s*<(Root|[A-Z][\w.]*)/.test(source);
if (!hasJsxReturnRoot(rootFileSource)) {
  throw new Error('Root file must return a literal JSX tree for Studio codemods');
}

Type guard

const isJsxRootReturn = (n: {type: string; argument?: {type: string} | null}): n is {type: 'ReturnStatement'; argument: {type: 'JSXElement' | 'JSXFragment'}} =>
  n.type === 'ReturnStatement' && (n.argument?.type === 'JSXElement' || n.argument?.type === 'JSXFragment');

Try / catch

try {
  editFolderInSource({codeMod, input});
} catch (e) {
  if (e instanceof Error && e.message === 'Could not find a root JSX element') {
    // fall back to manual edit / surface a user-facing hint
  }
}

Prevention

When it happens

Trigger: Executing a `move-composition-to-folder` codemod with `folderName: null` (move to root) whose source element is not lexically nested under a `return <Root>...</Root>` expression — e.g. the composition JSX is built in a variable, returned conditionally, or generated programmatically.

Common situations: A Root file that assembles the compositions tree dynamically (array.map into a variable, ternary returns, multiple return statements without a JSX argument, or a default export that is not a component returning JSX). Moving a composition back to the root level via Remotion Studio drag & drop then fails.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of remotion-dev/remotion@46a3a6bf13 (2026-09-18). Data as JSON: /api/errors/11da451105474b12. Report an issue: GitHub.

Appendix: source

Thrown at packages/studio-codemods/src/folder-source-edits.ts:143

const getRegistrationRoot = (
	path: recast.types.NodePath,
): JSXElement | JSXFragment => {
	let currentPath: recast.types.NodePath | null = path;
	while (currentPath !== null) {
		const node = currentPath.node as ReturnStatement;
		if (
			node.type === 'ReturnStatement' &&
			(node.argument?.type === 'JSXElement' ||
				node.argument?.type === 'JSXFragment')
		) {
			return node.argument;
		}

		currentPath = currentPath.parentPath ?? null;
	}

	throw new Error('Could not find a root JSX element');
};

const getNewFolderSourceEdits = ({
	ast,
	input,
}: {
	ast: File;
	input: string;
}): SourceEdit[] => {
	const snapshots = captureImportSnapshots(ast);
	const localName = ensureNamedImport({
		ast,
		importedName: 'Folder',
		sourcePath: 'remotion',
		localName: 'Folder',
	});
	let insertionEdit: SourceEdit | null = null;

View on GitHub (pinned to 46a3a6bf13)