Visual Builder

Shipkit integrates Builder.io for drag-and-drop page building. Non-technical users can create and edit pages visually without touching code.

Setup

NEXT_PUBLIC_BUILDER_API_KEY=your-builder-api-key
# Or: BUILDER_API_KEY (auto-mirrored to NEXT_PUBLIC_BUILDER_API_KEY)

Disable explicitly with DISABLE_BUILDER=true.

How It Works

  1. Register custom components in src/builder-registry.ts
  2. Create/edit pages in the Builder.io visual editor at builder.io/app
  3. Pages render at /builder.io/{slug} or through the unified catch-all route
  4. Content is fetched from Builder.io's API and rendered client-side
  5. Pages are cached with ISR (60-second revalidation)

Registered Components

Three components are registered for use in the visual editor:

Hero

Full-width hero section with title, subtitle, CTA button, and optional background image.

Props: title, subtitle, buttonText, buttonLink, backgroundImage

Stats

Statistics section with configurable grid layout.

Props: title, subtitle, stats[] (value, label, description), columns (2/3/4), background (white/gray)

CTA (Call-to-Action)

Section with heading, description, and up to two buttons. Supports background images.

Props: title, description, backgroundImage, primaryButton (text, link, variant), secondaryButton

Adding Custom Components

Create a component in src/components/modules/builder/:

// src/components/modules/builder/pricing.tsx
export function Pricing({ title, plans }: PricingProps) {
  return (
    <section>
      <h2>{title}</h2>
      {/* render plans */}
    </section>
  )
}

Register it in src/builder-registry.ts:

import { Builder } from "@builder.io/react"
import { Pricing } from "@/components/modules/builder/pricing"

Builder.registerComponent(Pricing, {
  name: "Pricing",
  inputs: [
    { name: "title", type: "string", defaultValue: "Pricing" },
    { name: "plans", type: "list", subFields: [
      { name: "name", type: "string" },
      { name: "price", type: "number" },
    ]},
  ],
})

The component will appear in the Builder.io visual editor's component panel.

Coexistence with Payload CMS

Shipkit checks Payload first, then Builder.io. If a page exists in Payload, it takes priority. Builder.io serves as a fallback for pages not managed in Payload.

Both systems can coexist: use Payload for structured content (blog posts, FAQs) and Builder.io for marketing/landing pages.

Preview Mode

The useIsPreviewing() hook from Builder.io detects when you're editing in the visual editor. Content renders even without being published, so you get live preview during editing.

Installing in Other Projects

Builder.io is available as a shadcn registry block:

npx shadcn add @shipkit/builder-io   # Standalone Builder.io integration
npx shadcn add @shipkit/cms-pages    # Shared Payload + Builder.io catch-all

See Component Registry for setup.

Key Files

FilePurpose
src/builder-registry.tsComponent registration
src/lib/builder-io/builder-io.tsxRenderer component (RenderBuilderContent)
src/app/(cms)/[...slug]/page.tsxUnified CMS catch-all (Payload + Builder.io)
src/app/(app)/(integrations)/builder.io/[...slug]/page.tsxStandalone Builder.io route
src/components/modules/builder/Builder-specific components (hero, cta, stats)
src/styles/builder-io.cssStyle overrides