How to implement editable regions for custom component in React with SDK Gen 2

The document doesn’t have an example on how to implement editable regions with React. I tried with Blocks component in Next.js but it throws error when I add the custom component in Visual Editor.
Here is what the Blocks look like:

<Blocks
	blocks={props.sectionA}
	parent={props.builderBlock.id}
	path='component.options.sectionA'
/>

And the registry:

{
	component: Hero,
	name: 'Hero',
	inputs: [
		{
			name: 'sectionA',
			type: 'uiBlocks',
			hideFromUI: true,
			helperText: 'This is an editable region.',
			defaultValue: [
				{
					'@type': '@builder.io/sdk:Element',
					component: {
						name: 'Text',
						options: {
							text: 'Section A Editable in Builder...',
						},
					},
				},
			],
		},
		{
			name: 'sectionB',
			type: 'uiBlocks',
			hideFromUI: true,
			helperText: 'This is an editable region.',
			defaultValue: [
				{
					'@type': '@builder.io/sdk:Element',
					component: {
						name: 'Text',
						options: {
							text: 'Section B Editable in Builder...',
						},
					},
				},
			],
		},
	],
}

This is the error in Next.js:

Warning: React.jsx: type is invalid – expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it’s defined in, or you might have mixed up default and named imports.

Logging the builderBlock returns this:

{
  "@type": "@builder.io/sdk:Element",
  "@version": 2,
  "id": "builder-3b94ef94ebeb40fbbfcbc1ff4cd1b02c",
  "component": {
    "name": "Hero",
    "options": {
      "sectionA": [
        {
          "@type": "@builder.io/sdk:Element",
          "@version": 2,
          "id": "builder-6ed7e374e928482ca8595c78c2636f0b",
          "component": {
            "name": "Text",
            "options": {
              "text": "Section A Editable in Builder..."
            }
          }
        }
      ],
      "sectionB": [
        {
          "@type": "@builder.io/sdk:Element",
          "@version": 2,
          "id": "builder-41e415a17612478ab11bab96c6ddaad8",
          "component": {
            "name": "Text",
            "options": {
              "text": "Section B Editable in Builder..."
            }
          }
        }
      ]
    }
  },
  "responsiveStyles": {
    "large": {
      "display": "flex",
      "flexDirection": "column",
      "position": "relative",
      "flexShrink": "0",
      "boxSizing": "border-box"
    }
  }
}

Hello @nghia.vi,

You may find help at the below links

I have read those documents thoroughly. I think the problem is the Blocks component itself from @builder.io/sdk-react-nextjs. Please read my question again I’ve included my code and the error.
And as I said, the docs doesn’t include example on how to use the Blocks component in Next.js specifically. I’ve followed the setup with other framework but the error still occur.

What I want to achieve is to have the + Add Block button in my custom component where children is expected. I have a Container component which accept children but when dragged into the Visual Editor the Container doesn’t have default height so it’s very hard for users to add children inside it by drag & drop. Also it’s harder for users to know if a custom component accept children or not. My thought is that Blocks component from @builder.io/sdk-react-nextjs package has some problem since I’ve tried everything, even try Blocks without any props, in RSC and not in RSC, but the error still occur.
I know I’m using an experimental package so my app can break here and there. But there is no where else I could report this

Hi. Is this issue still being investigated? I just want to be able to specify which parts of my custom component can user drag blocks into with SDK Gen 2 (@builder.io/sdk-react-nextjs). No matter what I do the Blocks component always throw error. I’m happy to provide my code for further insights, and if I was doing something wrong I want to know what it is and how can I fix the code to make it works.
Thanks in advance

Hello @nghia.vi,

We are currently investigating this issue. I suspect the problem may be related to the support for @builder.io/sdk-react-nextjs. It would be very helpful for our investigation if you could share the code for your implementation.

Thanks,

Sorry for the late reply.
Here is my CustomColumns implementation:

'use client';

import { Blocks, BuilderBlock } from '@builder.io/sdk-react-nextjs';
import { Box, Card, Grid } from '@mantine/core';
import Image from 'next/image';

import { PropsWithBuilderContext } from '@/types';

interface Column {
	blocks: BuilderBlock[];
	image?: string;
}

interface CustomColumnsProps {
	builderBlock?: BuilderBlock;
	columns?: Column[];
}

export const CustomColumns = ({ builderBlock, columns = [] }: PropsWithBuilderContext<CustomColumnsProps>) => {
	return (
		<Box p={30}>
			<Grid gutter='lg'>
				{columns.slice(0, 4).map((col, index) => (
					<Grid.Col
						key={index}
						miw={300}
						span={{ base: 12, md: 6 }}
					>
						<Card>
							<Card.Section>
								<div className='relative aspect-video'>
									<Image
										alt=''
										className='object-cover'
										fill
										src='/default-image.svg'
									/>
								</div>
							</Card.Section>
							<Blocks
								key={index}
								blocks={col.blocks}
								parent={builderBlock?.id}
								path={`component.options.columns.${index}.blocks`}
							/>
						</Card>
					</Grid.Col>
				))}
			</Grid>
		</Box>
	);
};

this is the registry:

import { RegisteredComponent } from '@builder.io/sdk-react-nextjs';

import { CustomColumns } from '.';

export const CustomColumnsEntry: RegisteredComponent = {
	component: CustomColumns,
	name: 'CustomColumns',
	inputs: [
		{
			name: 'columns',
			type: 'array',
			defaultValue: [
				{
					image:
						'https://cdn.builder.io/api/v1/image/assets%2Fpwgjf0RoYWbdnJSbpBAjXNRMe9F2%2Ffb27a7c790324294af8be1c35fe30f4d',
					blocks: [
						{
							'@type': '@builder.io/sdk:Element',
							component: {
								name: 'Text',
								options: {
									text: 'Enter some text...',
								},
							},
							responsiveStyles: {
								large: {
									display: 'flex',
									flexDirection: 'column',
									position: 'relative',
									flexShrink: '0',
									boxSizing: 'border-box',
									marginTop: '20px',
									lineHeight: 'normal',
									height: 'auto',
									textAlign: 'center',
								},
							},
						},
					],
				},
				{
					image:
						'https://cdn.builder.io/api/v1/image/assets%2Fpwgjf0RoYWbdnJSbpBAjXNRMe9F2%2Ffb27a7c790324294af8be1c35fe30f4d',
					blocks: [
						{
							'@type': '@builder.io/sdk:Element',
							component: {
								name: 'Text',
								options: {
									text: 'Enter some text...',
								},
							},
							responsiveStyles: {
								large: {
									display: 'flex',
									flexDirection: 'column',
									position: 'relative',
									flexShrink: '0',
									boxSizing: 'border-box',
									marginTop: '20px',
									lineHeight: 'normal',
									height: 'auto',
									textAlign: 'center',
								},
							},
						},
					],
				},
			],
			subFields: [
				{
					name: 'image',
					type: 'file',
					allowedFileTypes: ['jpeg', 'jpg', 'png', 'svg'],
					required: true,
					defaultValue:
						'https://cdn.builder.io/api/v1/image/assets%2Fpwgjf0RoYWbdnJSbpBAjXNRMe9F2%2Ffb27a7c790324294af8be1c35fe30f4d',
				},
				{
					name: 'blocks',
					type: 'blocks',
					hideFromUI: true,
					helperText: 'This is an editable region where you can drag and drop blocks.',
					defaultValue: [
						{
							'@type': '@builder.io/sdk:Element',
							component: {
								name: 'Text',
								options: {
									text: 'Enter some text...',
								},
							},
							responsiveStyles: {
								large: {
									display: 'flex',
									flexDirection: 'column',
									position: 'relative',
									flexShrink: '0',
									boxSizing: 'border-box',
									marginTop: '20px',
									lineHeight: 'normal',
									height: 'auto',
									textAlign: 'center',
								},
							},
						},
					],
				},
			],
		},
	],
};

props.builderBlock:

{
  "@type": "@builder.io/sdk:Element",
  "@version": 2,
  "id": "builder-55dd460b4268467a8fbdbc6dd3d324ae",
  "component": {
    "name": "CustomColumns",
    "options": {
      "columns": [
        {
          "image": "https://cdn.builder.io/api/v1/image/assets%2Fpwgjf0RoYWbdnJSbpBAjXNRMe9F2%2Ffb27a7c790324294af8be1c35fe30f4d",
          "blocks": [
            {
              "@type": "@builder.io/sdk:Element",
              "@version": 2,
              "id": "builder-489f9f510068476396d1d60c273eeb16",
              "component": {
                "name": "Text",
                "options": {
                  "text": "Enter some text..."
                }
              },
              "responsiveStyles": {
                "large": {
                  "display": "flex",
                  "flexDirection": "column",
                  "position": "relative",
                  "flexShrink": "0",
                  "boxSizing": "border-box",
                  "marginTop": "20px",
                  "lineHeight": "normal",
                  "height": "auto",
                  "textAlign": "center"
                }
              }
            }
          ]
        },
        {
          "image": "https://cdn.builder.io/api/v1/image/assets%2Fpwgjf0RoYWbdnJSbpBAjXNRMe9F2%2Ffb27a7c790324294af8be1c35fe30f4d",
          "blocks": [
            {
              "@type": "@builder.io/sdk:Element",
              "@version": 2,
              "id": "builder-b875cfec35534ba99f844c782678fb91",
              "component": {
                "name": "Text",
                "options": {
                  "text": "Enter some text..."
                }
              },
              "responsiveStyles": {
                "large": {
                  "display": "flex",
                  "flexDirection": "column",
                  "position": "relative",
                  "flexShrink": "0",
                  "boxSizing": "border-box",
                  "marginTop": "20px",
                  "lineHeight": "normal",
                  "height": "auto",
                  "textAlign": "center"
                }
              }
            }
          ]
        }
      ]
    }
  },
  "responsiveStyles": {
    "large": {
      "display": "flex",
      "flexDirection": "column",
      "position": "relative",
      "flexShrink": "0",
      "boxSizing": "border-box"
    }
  }
}

I’m getting these errors:

Warning: React.jsx: type is invalid – expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it’s defined in, or you might have mixed up default and named imports.
at Ee (webpack-internal:///(ssr)/./node_modules/.pnpm/@builder.io+sdk-react-nextjs@0.14.21_next@14.2.3_@babel+core@7.24.4_react-dom@18.3.1_react@18_phiha7xbsb6cfmiixd7orjunka/node_modules/@builder.io/sdk-react-nextjs/lib/node/USE_CLIENT_BUNDLE-14685e18.js:136:86)

Internal error: Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it’s defined in, or you might have mixed up default and named imports.
at aw (/home/nghiavi/prism-builder/node_modules/.pnpm/next@14.2.3_@babel+core@7.24.4_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:35:46775)

if I remove the <Blocks /> the component display correctly.

I tried with RSC but the errors look similar, in additional of this error but I don’t know if it’s related:

⨯ TypeError: Cannot read properties of undefined (reading ‘localState’)
at stringify ()
at stringify ()
at stringify ()
at stringify ()
at stringify ()
digest: “3223408045”

RSC version:

import { Blocks, BuilderBlock } from '@builder.io/sdk-react-nextjs';
import { Box, Card, CardSection, Grid, GridCol } from '@mantine/core';
import Image from 'next/image';

import { PropsWithBuilderContext } from '@/types';

interface Column {
	blocks: BuilderBlock[];
	image?: string;
}

interface CustomColumnsProps {
	builderBlock?: BuilderBlock;
	columns?: Column[];
}

export const CustomColumns = ({ builderBlock, columns = [] }: PropsWithBuilderContext<CustomColumnsProps>) => {
	return (
		<Box p={30}>
			<Grid gutter='lg'>
				{columns.slice(0, 4).map((col, index) => (
					<GridCol
						key={index}
						miw={300}
						span={{ base: 12, md: 6 }}
					>
						<Card>
							<CardSection>
								<div className='relative aspect-video'>
									{col.image ? (
										<Image
											alt=''
											className='object-cover'
											fill
											src={col.image ?? '/default-image.svg'}
										/>
									) : null}
								</div>
							</CardSection>
							<Blocks
								key={index}
								blocks={col.blocks}
								parent={builderBlock?.id}
								path={`component.options.columns.${index}.blocks`}
							/>
						</Card>
					</GridCol>
				))}
			</Grid>
		</Box>
	);
};
import { RegisteredComponent } from '@builder.io/sdk-react-nextjs';

import { CustomColumns } from '.';

export const CustomColumnsEntry: RegisteredComponent = {
	component: CustomColumns,
	name: 'CustomColumns',
	isRSC: true,
	inputs: [
		{
			name: 'columns',
			type: 'array',
			defaultValue: [
				{
					image:
						'https://cdn.builder.io/api/v1/image/assets%2Fpwgjf0RoYWbdnJSbpBAjXNRMe9F2%2Ffb27a7c790324294af8be1c35fe30f4d',
					blocks: [
						{
							'@type': '@builder.io/sdk:Element',
							component: {
								name: 'Text',
								options: {
									text: 'Enter some text...',
								},
							},
							responsiveStyles: {
								large: {
									display: 'flex',
									flexDirection: 'column',
									position: 'relative',
									flexShrink: '0',
									boxSizing: 'border-box',
									marginTop: '20px',
									lineHeight: 'normal',
									height: 'auto',
									textAlign: 'center',
								},
							},
						},
					],
				},
				{
					image:
						'https://cdn.builder.io/api/v1/image/assets%2Fpwgjf0RoYWbdnJSbpBAjXNRMe9F2%2Ffb27a7c790324294af8be1c35fe30f4d',
					blocks: [
						{
							'@type': '@builder.io/sdk:Element',
							component: {
								name: 'Text',
								options: {
									text: 'Enter some text...',
								},
							},
							responsiveStyles: {
								large: {
									display: 'flex',
									flexDirection: 'column',
									position: 'relative',
									flexShrink: '0',
									boxSizing: 'border-box',
									marginTop: '20px',
									lineHeight: 'normal',
									height: 'auto',
									textAlign: 'center',
								},
							},
						},
					],
				},
			],
			subFields: [
				{
					name: 'image',
					type: 'file',
					allowedFileTypes: ['jpeg', 'jpg', 'png', 'svg'],
					required: true,
					defaultValue:
						'https://cdn.builder.io/api/v1/image/assets%2Fpwgjf0RoYWbdnJSbpBAjXNRMe9F2%2Ffb27a7c790324294af8be1c35fe30f4d',
				},
				{
					name: 'blocks',
					type: 'blocks',
					hideFromUI: true,
					helperText: 'This is an editable region where you can drag and drop blocks.',
					defaultValue: [
						{
							'@type': '@builder.io/sdk:Element',
							component: {
								name: 'Text',
								options: {
									text: 'Enter some text...',
								},
							},
							responsiveStyles: {
								large: {
									display: 'flex',
									flexDirection: 'column',
									position: 'relative',
									flexShrink: '0',
									boxSizing: 'border-box',
									marginTop: '20px',
									lineHeight: 'normal',
									height: 'auto',
									textAlign: 'center',
								},
							},
						},
					],
				},
			],
		},
	],
};

here is the page.tsx rendering the custom component:

export default async function Page({ params, searchParams }: PageProps) {
	const urlPath = (params.template ? `/${params.template}` : '') + '/' + (params.page?.join('/') ?? '');
	const configuration = await getWebsiteConfiguration({ urlPath });

	if (!isWebsiteConfigurationValid(configuration)) {
		return <MissingBuilderConfigAlert configuration={configuration} />;
	}

	const cmsEndpoint = configuration.cmsEndpoint;
	const stableId = configuration.stableId;

	const pageContent = await fetchOneEntry({
		options: getBuilderOptions('page', searchParams),
		apiKey: PUBLIC_API_KEY,
		model: 'page',
		userAttributes: { urlPath },
		cacheSeconds: 0,
		staleCacheSeconds: 0,
	});

	const canShowContent = !!pageContent || isPreviewing(searchParams) || isEditing(searchParams);

	if (!canShowContent) {
		return <NotFound />;
	}

	const [{ responseData: webConfiguration }, { responseData: stableCustomFields }] = cmsEndpoint
		? await Promise.all([
				fetch(`${cmsEndpoint}/web/configuration`).then(parseResponseBody<ResponseData<WebConfiguration>>),
				fetch(`${cmsEndpoint}/web/stable_custom_field`).then(parseResponseBody<ResponseData<StableCustomField[]>>),
			])
		: [{ responseData: null }, { responseData: null }];

	const customFields = stableCustomFields
		? stableCustomFields.reduce<Record<string, Omit<StableCustomField, 'media'>>>(
				(fields, field) => ({
					...fields,
					[field.keyCode]: field,
				}),
				{},
			)
		: {};

	return (
		<main>
			<Content
				apiKey={PUBLIC_API_KEY}
				content={pageContent}
				customComponents={customComponents}
				linkComponent={Link}
				model='page'
				data={{
					searchParams,
					cmsEndpoint,
					configuration: webConfiguration,
					customFields,
					stableId,
				}}
			/>
		</main>
	);
}

I’ve built the site with CustomColumns code so you can take a look.

Hello @nghia.vi,

The error you’re encountering, “Element type is invalid,” typically happens when React cannot properly identify the component intended for rendering. The most common reasons for this are:

  1. Incorrect Imports/Exports: You might be incorrectly importing or exporting components.
  2. Undefined Components: The component could be undefined because it’s not correctly imported.

First, ensure that you correctly import and export your component. Here’s how to verify step-by-step:

Verify Exports

Ensure CustomColumns is correctly exported in its definition file:

// inside your CustomColumns.tsx or index.tsx
export const CustomColumns = ({ 
  builderBlock, columns = [] }: PropsWithBuilderContext<CustomColumnsProps>
) => {
  return (
    <Box p={30}>
      <Grid gutter="lg">
        {columns.slice(0, 4).map((col, index) => (
          <Grid.Col
            key={index}
            miw={300}
            span={{ base: 12, md: 6 }}
          >
            <Card>
              <Card.Section>
                <div className="relative aspect-video">
                  <Image
                    alt=""
                    className="object-cover"
                    fill
                    src="/default-image.svg"
                  />
                </div>
              </Card.Section>
              <Blocks
                key={index}
                blocks={col.blocks}
                parent={builderBlock?.id}
                path={`component.options.columns.${index}.blocks`}
              />
            </Card>
          </Grid.Col>
        ))}
      </Grid>
    </Box>
  );
};

Verify Imports

Ensure you are properly importing CustomColumns in your registry file:

import { RegisteredComponent } from '@builder.io/sdk-react-nextjs';
import { CustomColumns } from './path-to-your-component/CustomColumns';

export const CustomColumnsEntry: RegisteredComponent = {
  component: CustomColumns,
  name: 'CustomColumns',
  inputs: [
    {
      name: 'columns',
      type: 'array',
      defaultValue: [
        // Column structure
      ],
      subFields: [
        // Subfield structure
      ],
    },
  ],
};

Make sure that the path-to-your-component correctly points to the location where CustomColumns is defined. Typically, you would not have a nested export within another file’s import path.

Validate Dynamic Imports (Next.js Specific)

If you are using dynamic imports in Next.js, ensure your import looks something like this:

// usage of dynamic import
import dynamic from 'next/dynamic';
const CustomColumns = dynamic(() => import('./path-to-your-component/CustomColumns'));

Debug Undefined Component

Add a console log in the file where CustomColumns is defined and imported, to ensure it’s being properly imported:

console.log(CustomColumns); // Should not be undefined

Helper Verification Key

Ensure other parts of your codebase have no naming conflicts or inherent issues:

import { Builder, withChildren } from "@builder.io/react";

const Page = (props) => {
  return (
    <>
      <header>
        <h1>My Page</h1>
      </header>
      <main>
        <CustomColumns {...props} />
      </main>
    </>
  );
};

Builder.registerComponent(withChildren(Page), {
  name: "CustomColumns",
  inputs: [{ name: "columns", type: "array" }],
});

Ensure Correct Integration

Refer to the official Custom Components Integration Guide to verify you have followed all steps appropriately.

After following these steps, if the issue persists, please check your project configuration, including any extending configurations potentially overriding these setups.

For rendering the Editor component, ensure you’ve properly set the environment:

import { BuilderComponent } from '@builder.io/react';

// In your top-level component/prerender logic
return (
  <BuilderComponent model="your-model-name" content={props.content} />
);

Once you’ve verified and corrected all potential issues, restart your development server using:

npm run dev

This should fix the undefined component issue. If the error persists, there may be more related to next-sdk which is currently BETA and this feature of editable reason is not fully supported.

Thanks,

1 Like

Thanks for your helpful reply. Actually I managed to make it work now after taking some time looked deeper into the source code and it’s props definition. It turns out that I wasn’t passing necessary props to the <Blocks /> component. I guess I’ll share my code here so that if anyone having the same issue can learn from my mistake.

IMPORTANT: if you have isRSC: true in any custom component, <Blocks /> can only be used in RSC because of passing builderComponents to registeredComponents props. Workaround to use <Blocks /> in client component is to pass it as props like my example below. More info: Rendering: Composition Patterns | Next.js (nextjs.org)

BurgerButton.tsx:

import { Blocks, BuilderBlock, RegisteredComponent } from '@builder.io/sdk-react-nextjs';
import { BurgerProps, DrawerProps, MantineSize } from '@mantine/core';

import { BurgerButtonClient } from './BurgerButtonClient';
import { CSSProperties, FunctionComponent, ReactNode } from 'react';
import { BuilderContext } from '../../types/builder';

export interface BuilderBlockAttributes {
	className?: string;
	href?: string;
	style?: CSSProperties;
}

// copy from '../../types/builder'
export type BuilderBlockProps<
	P = unknown,
	L extends { href?: string; children?: ReactNode } = { href?: string; children?: ReactNode },
> = P & {
	attributes?: BuilderBlockAttributes;
	builderBlock: BuilderBlock;
	builderContext: BuilderContext;
	builderLinkComponent?: FunctionComponent<L>;
	builderComponents: Record<string, RegisteredComponent>;
	children?: ReactNode;
};

interface BurgerButtonProps extends BurgerProps {
	blocks?: BuilderBlock[];
	drawerProps?: Omit<DrawerProps, 'offset'> & {
		offset?: {
			top?: number;
			right?: number;
			bottom?: number;
			left?: number;
		};
		size?: MantineSize;
	};
}

export const BurgerButton = ({
	attributes,
	blocks,
	// these props are passed by Builder when this component is used in Visual Editor
	builderBlock,
	builderComponents,
	builderContext,
	builderLinkComponent,
	// end
	color,
	drawerProps,
	lineSize,
	size,
}: BuilderBlockProps<BurgerButtonProps>) => {
	return (
		<BurgerButtonClient
			attributes={attributes}
			color={color}
			drawerProps={drawerProps}
			lineSize={lineSize}
			size={size}
		>
			<Blocks
				blocks={blocks}
				context={builderContext} // was missing
				linkComponent={builderLinkComponent}  // was missing, not necessary if you don't use linkComponent prop on <Content />
				parent={builderBlock.id}
				path='component.options.blocks'
				registeredComponents={builderComponents}  // was missing
			/>
		</BurgerButtonClient>
	);
};

BurgerButtonClient.tsx:

'use client';

import { Burger, BurgerProps, Drawer, DrawerProps } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import { omit } from 'lodash';

import { BuilderBlockProps } from '../../types/builder';

interface BurgerButtonClientProps extends Pick<BuilderBlockProps, 'attributes' | 'children'>, BurgerProps {
	drawerProps?: Omit<DrawerProps, 'offset'> & {
		offset?: {
			top?: number;
			right?: number;
			bottom?: number;
			left?: number;
		};
	};
}

export const BurgerButtonClient = ({
	attributes,
	children,
	color,
	drawerProps,
	lineSize,
	size,
}: BurgerButtonClientProps) => {
	const [opened, { toggle, close }] = useDisclosure();

	const { bottom, left, right, top } = drawerProps?.offset ?? {};

	return (
		<>
			<Burger
				{...attributes}
				color={color}
				lineSize={lineSize}
				onClick={toggle}
				opened={opened}
				size={size}
			/>
			<Drawer
				{...omit(drawerProps, 'offset')}
				onClose={close}
				opened={opened}
				overlayProps={{
					bottom,
					left,
					right,
					top,
				}}
				styles={{
					inner: {
						bottom,
						left,
						right,
						top,
					},
				}}
			>
				{children}
			</Drawer>
		</>
	);
};

BurgerButton.register.ts:

import { RegisteredComponent } from '@builder.io/sdk-react-nextjs';

import { BurgerButton } from './BurgerButton';

const BurgerButtonEntry: RegisteredComponent = {
	component: BurgerButton,
	name: 'BurgerButton',
	noWrap: true,
	isRSC: true,
	inputs: [
		{
			name: 'color',
			type: 'string',
		},
		{
			name: 'lineSize',
			type: 'number',
			helperText: 'Line size in pixel',
		},
		{
			name: 'size',
			type: 'string',
		},
		{
			name: 'drawerProps',
			type: 'object',
			friendlyName: 'Drawer options',
			subFields: [
				{
					name: 'offset',
					type: 'object',
					subFields: [
						{ name: 'top', type: 'number' },
						{ name: 'right', type: 'number' },
						{ name: 'bottom', type: 'number' },
						{ name: 'left', type: 'number' },
					],
				},
				{ name: 'position', type: 'string', enum: ['left', 'right', 'top', 'bottom'], defaultValue: 'left' },
				{ name: 'withCloseButton', type: 'boolean', defaultValue: false },
			],
		},
		{
			name: 'blocks',
			type: 'uiBlocks',
			hideFromUI: true,
			defaultValue: []
		},
	],
};

export { BurgerButtonEntry as BurgerButton };

I really think the documentation should’ve mention this usage somewhere. I hope when @builder.io/sdk-react-nextjs become stable there is an example like mine included in the documentation.

Thanks again for looking into my problem.

One more thing worth pointing out. My example above works very well with SSR/ISR. No circular dependencies error encounter and no error from Next.js regarding usage of RSC in client components either since every registered components are being used as props.