What you need#
Visual Builder setup has three matching pieces:
- A CMS Component Type and block code, such as
hero_banner. - A React component that can render that block's data.
- A registry entry with the exact same code.
The CMS stores data and page placement. Your frontend owns the visual design.
1. Create a component registry#
Keep block mappings in one module.
import type { ComponentRegistry } from "@contoprix/react";
import HeroBanner from "@/components/contoprix/HeroBanner";
import CallToAction from "@/components/contoprix/CallToAction";
const components: ComponentRegistry = {
hero_banner: HeroBanner,
call_to_action: CallToAction,
};
export default components;The key is the Component Type code from Contoprix. It is case-sensitive from the registry's point of view, so copy the code exactly instead of using the display name.
2. Add one component#
A Component Type determines the fields editors can fill in. For example, a hero_banner type might contain eyebrow, heading, description, and button_url.
import type { ContoprixComponentProps } from "@contoprix/react";
type HeroSettings = {
eyebrow?: string;
heading?: string;
description?: string;
button_label?: string;
button_url?: string;
};
export default function HeroBanner({
settings,
previewAttributes,
}: ContoprixComponentProps) {
const hero = settings as HeroSettings | undefined;
if (!hero?.heading) return null;
return (
<section {...previewAttributes}>
{hero.eyebrow ? <p>{hero.eyebrow}</p> : null}
<h1>{hero.heading}</h1>
{hero.description ? <p>{hero.description}</p> : null}
{hero.button_label && hero.button_url ? (
<a href={hero.button_url}>{hero.button_label}</a>
) : null}
</section>
);
}Spread previewAttributes on the root element. They are harmless on public delivery and let the Visual Builder select this block in preview.
3. Add schema fallback#
A pulled Contoprix schema lets PageRenderer render an unregistered component block with the generic renderer while a developer prepares custom UI.
import { buildSchemaRegistry } from "@contoprix/react";
import type { SdkSchema } from "@contoprix/client";
import pulledSchema from "../../.contoprix/schema/schema.json";
export const schemas = buildSchemaRegistry(pulledSchema as SdkSchema);Then pass the schema registry to the renderer:
"use client";
import { PageRenderer } from "@contoprix/react/client";
import type { ContoprixPage } from "@contoprix/types";
import components from "./components";
import { schemas } from "./schema";
export function ContoprixRenderer({ page }: { page: ContoprixPage }) {
return <PageRenderer page={page} components={components} schemas={schemas} />;
}The generic fallback does not replace custom components for forms or content-entry blocks. Register a custom renderer for those shapes.
4. Fetch and render a published page#
Use the server helper to fetch by public route path, then pass the complete page to your renderer.
import { getContoprixPage } from "@contoprix/next/server";
import { ContoprixRenderer } from "@/contoprix/ContoprixRenderer";
export default async function CmsPage({
params,
}: {
params: Promise<{ slug: string[] }>;
}) {
const { slug } = await params;
const page = await getContoprixPage({
slug: "/" + slug.join("/"),
languageCode: "en",
});
return <ContoprixRenderer page={page} />;
}The root page can use client.pages.get(). See the Pages Routing guide for 404 handling and root-route setup.
5. Pull and validate the schema#
After a content model changes, refresh the local schema and generated types.
npx contoprix pull
npx contoprix generate
npx contoprix components
npx contoprix validatepulldownloads the current schema.generatecreates TypeScript helpers for the schema.componentsscaffolds custom React overrides and a registry.validatereports custom overrides versus generic fallback coverage.
Generated files are a starting point. Review component field types, use your design system, and add error/empty states before treating a scaffold as production UI.
Setup checklist#
- CMS Component Type code and registry key match exactly.
- Every important
componentblock has a custom renderer or a known generic schema. - Forms and content-entry blocks have an intentional custom renderer.
PageRendereris imported from@contoprix/react/client.- The page is fetched on the server with delivery credentials.
- Schema files are refreshed after model changes.
- The page has been tested in preview and published delivery.
Common setup issues#
| Problem | Cause | Fix |
|---|---|---|
| A block is a missing-component placeholder | The code is absent or mismatched in the registry. | Match the exact CMS code. |
| A new component looks generic | The schema is available but there is no custom override yet. | Add a custom component when design is ready. |
| A new component is missing entirely | The local schema was not refreshed. | Run npx contoprix pull and check the type/code. |
| Visual editing cannot select a component | The root element omits previewAttributes. | Spread it onto the component's outer element. |
| Preview credentials appear in browser code | Server/client boundaries are mixed. | Fetch preview data only in server code or a protected server route. |