BTST
PluginsQuickstartDocs
Live Blog

From research to product evaluation

Evaluating a publishing workflow for an app you already own?

See what the BTST Blog plugin adds to an existing React or Next.js app
BTST

Open-source TypeScript features for the React application, data, and deployment you already own.

Released plugins

  • Blog
  • AI Chat
  • CMS
  • Form Builder
  • UI Builder
  • Kanban
  • Comments
  • Media
  • Route Docs
  • OpenAPI
  • Better Auth UI

Resources

  • Quickstart
  • Documentation
  • All plugins
  • Live Blog
  • GitHub (opens in a new tab)
© 2026 BTST. Open source under the MIT License.
AI Chat
September 12, 2026ReactUI Builder

React Visual Page Builders: Share the Component Registry with the Renderer

Connect an approved component registry to visual editing and public rendering, and plan for stored layouts, publication status, and component changes.

React Visual Page Builders: Share the Component Registry with the Renderer

A visual page can look correct in the editor and fail on its public route when the two surfaces use different component registries. Treat the registry as part of the stored content contract: a saved layer names a component that the deployed renderer must still understand.

BTST UI Builder stores page layouts through the CMS plugin. It provides the editor and rendering components; your application chooses allowed components, public routes, and publication rules. This guide uses @btst/stack@3.0.0.

Define the allowed components deliberately#

A registry associates a layer type with a React component and its editable prop schema. Editors can compose the components you register. Start with the small set your product needs, such as a heading, text section, image, and call to action. Add choices when they solve a recurring editorial need.

Use stable registry keys. Renaming Hero to MarketingHero in code does not automatically rename that type in previously saved layers. Before removing or renaming a key, inspect stored pages and migrate or retain compatibility for their references.

Prop changes need the same care. A new required prop can invalidate an older page even if newly created pages work. Preview at least one existing page with the new registry before deploying it. The registry documentation covers custom registries, schemas, and parent-child restrictions.

Use the same registry when rendering#

Pass the chosen registry to the UI Builder client plugin. The released PageRenderer resolves its registry in this order: a registry explicitly supplied to the renderer, the plugin factory's registry, then the built-in defaults. An accidental standalone override can therefore make the public route differ from the editor.

Choose the renderer according to where data is already available:

RendererData responsibilityIntended context
PageRendererFetches a page by slugClient component under the configured stack
LayerRendererReceives already loaded layersClient rendering with application-controlled fetching
ServerLayerRendererReceives already loaded layersServer rendering, including SSR and RSC

For a server-rendered page, load and authorize the record on the server, parse its saved layer data, and supply the intended registry. Register components that work in the selected rendering context; choosing a server renderer does not make every interactive component server-compatible.

If search visibility matters, verify the ordinary HTML response contains the page's meaningful text. Also supply a title, description, canonical URL, and sitemap entry from the public route. The visual editor does not decide the framework's metadata policy for you.

Make publication a server decision#

UI Builder's content model has draft, published, and archived status values. Do not infer a public access boundary from the field's name. The released slug query selects by slug, and the renderer is not a general draft-protection layer.

For a public page implementation, require published status in the server read path before returning content. Apply that rule to every anonymously reachable path that can return the record, including CMS reads. A client-side status check after fetching cannot protect data already sent to the browser.

Keep authenticated preview separate from public rendering. A reviewer may intentionally load a draft, while the public route should return the application's normal not-found response for the same draft. Raw server/build getters are lower-level access; calling one does not automatically apply a request user's policy.

UI Builder reuses CMS permissions. Review cms.record.read with its collection versus record scope and the separate cms.contentType.read check. Allowing a page record for public rendering should not unintentionally expose the administrative collection. Use the CMS documentation alongside the builder setup.

Test saved pages across a deployment#

Create one published page and one draft with a registered component, a variable, and an image. Verify the published page anonymously on desktop and mobile, including an initial server response if SSR is required. Confirm the draft stays unavailable through both the page route and any public content endpoint.

Change a compatible prop default, deploy to a test environment, and reopen the old page in both the editor and renderer. Then test an unknown component key and malformed stored layer data. Provide a controlled error state and retain the original record for repair rather than silently replacing it with an empty page.

Use UI Builder when operators need to arrange approved components. Use CMS content relationships when the main problem is shared structured records. These can work together, but each needs its own validation and publication checks.

In This Post

Define the allowed components deliberatelyUse the same registry when renderingMake publication a server decisionTest saved pages across a deployment