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
- Restructure the Root file so the compositions/folders are statically written inside a single `return (<Root>...</Root>)` JSX expression
- Move the target composition element out of any helper function or variable into the main returned JSX tree
- 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
- Keep the compositions tree as a single static `return (<Root>...</Root>)` expression
- Avoid building the tree in variables or helper functions
- Don't refactor Root files with code transforms that strip JSX literals
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
- Cannot duplicate JSX element with no parent
- Cannot split a JSX sequence without a source location
- Cannot split JSX sequence with no parent
- Could not find a root JSX element
- Could not find as a direct JSX child
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)