Components
Add New Component Variant

🧩 XSite – Add New Component Variant Documentation

This document provides the complete step-by-step process to create, generate, publish, and register a new component variant in the XSite ecosystem.


πŸ— System Overview

A new component flows through the following pipeline:

Strapi β†’ Registry Generation β†’ Plop Scaffold β†’ UI Implementation β†’ Versioning β†’ Staging Publish β†’ Production Promotion β†’ xsite-builder Sync β†’ Editor Schema β†’ Schema Loader

βœ… Process to Add New Component Variants

1) Start Strapi

Start the Strapi development server:

pnpm --filter strapi dev

2) Create component in Strapi Content Builder

Navigate to:

Strapi β†’ Content Builder β†’ Create Component

⚠️ IMPORTANT NOTE (Must Follow)

Never forget to add the following field in each component:

cmp_variant: string

If you forget cmp_variant:

  • ❌ The component will not be installed in a new project after build.
  • ❌ Registry generation may break or omit the component.
  • ❌ Component rendering may fail depending on install logic.

Always verify this field before saving the component.


3) Generate Strapi and Global Registry

After adding the component in Strapi, generate registries using:

pnpm registry:strapi
pnpm registry:global

This updates:

  • Strapi registry outputs/types
  • Global registry outputs used by the platform

Generate Intefaces

Also generate interface of recently added components using:

pnpm generate:types

this will update interface.ts file with the types of your recent component which will later used to generate components.


4) List the component for Plop generator

To make components available in the Plop generator, list them in category config files:

  • If it is a single component, add it to: categories.single.json
  • If it is a collection component, add it to: categories.collection.json

Single component example

{
  "name": "card",
  "type": "dynamic-zone"
}

Collection component example

{
  "name": "portfolio",
  "iName": "Portfolio"
}

This step makes the component appear in the plop generation flow.


5) Generate new component using Plop

Run:

pnpm plop

It will display two categories:

  • Single (Hero, CTA, FAQ)
  • Collection (Blog, Products, Events)

Select the type of component you want to create.

Then it will display the variants list (example):

hero
cta
faq
testimonial
about
header
footer

Naming rule

If you select hero, then the component name should follow:

  • hero1, hero2, hero3, ...

If you select header, then:

  • header1, header2, ...

In short:

{category}{number}

6) Check if the component is Client or Server

If the component uses:

  • useState, useEffect
  • browser APIs
  • event handlers
  • interactive UI logic

Then it must be a Client Component.

Update tsup config for client components

In your tsup.config.ts (or relevant build config), add:

banner: {
  js: '"use client";',
},
esbuildOptions(options) {
  options.banner = {
    js: '"use client";',
  };
},

Also update output formats:

format: ["cjs", "esm"],

7) Update meta.ts

Update the component metadata correctly.

Example:

import { ComponentMetaInterface } from "@interfaces/registry/meta";
import { data } from "./data";
import Schema from "./schema.json";
 
export const meta: ComponentMetaInterface = {
  key: "hero:hero1",
  name: "hero1",
  category: "hero",
  type: "dynamic-zone",
  kind: "component",
  description:
    "A classic hero section with primary heading, subtitle and call-to-action support. Ideal for homepages and marketing landing pages.",
  previewImage:
    "https://xsite-components.s3.ap-south-1.amazonaws.com/hero/hero1.png",
  tags: ["hero", "landing", "homepage", "marketing", "dynamic-zone"],
  props: data,
  schema: Schema,
  availability: {
    plans: ["free", "pro"],
  },
};

Meta checklist

  • key: must be unique, use category:name (example: hero:hero1)
  • name: matches the component/package variant name
  • category: must match the group (hero/cta/faq/etc.)
  • type: dynamic-zone or global (as per your system)
  • previewImage: valid URL (recommended)
  • tags: searchable keywords
  • props: default demo data
  • schema: RJSF JSON schema
  • availability.plans: allowed plans

8) Publish workflow (Changesets β†’ Staging β†’ Production)

8.1) Create changeset

This helps specify the changes made in the component (major/minor/patch) and stores a description.

npx changeset

Choose one:

  • patch β†’ bug fix
  • minor β†’ feature addition
  • major β†’ breaking change

Write a specific message for what changed.

8.2) Apply version bump

This updates the package version based on the changeset:

npx changeset version

9) Publish the component to staging (Verdaccio)

Publish to staging:

pnpm publish:staging @xsite/ui-hero1

10) Promote the package to production

After publishing and verifying staging, promote to production:

pnpm promote:production @xsite/ui-hero1@1.0.2

This points production to the specified version.


11) Add latest components to xsite-builder dependencies

After publishing components from ui-builder, add them to:

xsite-builder/package.json

Example:

{
  "@xsite/ui-about1": "staging",
  "@xsite/ui-about2": "staging",
  "@xsite/ui-blog1": "staging",
  "@xsite/ui-contact1": "staging",
  "@xsite/ui-cta1": "staging",
  "@xsite/ui-cta2": "staging"
}

12) Install latest packages

Sync and install the latest staging dependencies:

pnpm sync:deps -- --staging
pnpm install

🧾 Component Editor Schema Setup (Required for New Variants)

For the component editor, we need to add schema for each new component variant.

We don’t need to add schema again for variants that already have schemas.

Example schema path:

/docs/schemas/contact.schema.json

Example contact.schema.json

{
  "title": "Contact Component",
  "type": "object",
  "required": [],
  "properties": {
    "title": { "title": "Title", "type": "string" },
    "subtitle": { "title": "Subtitle", "type": "string" },
    "description": { "title": "Description", "type": "string" },
    "contactInfo": { "title": "Contact Info", "$ref": "#/definitions/contactInfo" },
    "form": { "title": "Form", "$ref": "#/definitions/form" }
  },
  "definitions": {
    "contactInfo": {
      "type": "object",
      "required": ["heading", "sub_heading", "items"],
      "properties": {
        "heading": { "type": "string" },
        "sub_heading": { "type": "string" },
        "items": {
          "type": "array",
          "minItems": 1,
          "items": { "$ref": "#/definitions/contactItem" }
        }
      }
    },
    "contactItem": {
      "type": "object",
      "required": ["label", "value"],
      "properties": {
        "label": { "type": "string" },
        "value": { "type": "string" },
        "link": { "type": "string" }
      }
    },
    "form": {
      "type": "object",
      "required": [
        "heading",
        "sub_heading",
        "fields",
        "button",
        "successMessage",
        "errorMessage"
      ],
      "properties": {
        "heading": { "type": "string" },
        "sub_heading": { "type": "string" },
        "fields": {
          "type": "array",
          "minItems": 1,
          "items": { "$ref": "#/definitions/formField" }
        },
        "button": { "$ref": "#/definitions/button" },
        "successMessage": { "type": "string" },
        "errorMessage": { "type": "string" }
      }
    },
    "formField": {
      "type": "object",
      "required": ["name", "label", "type"],
      "properties": {
        "name": { "type": "string" },
        "label": { "type": "string" },
        "type": { "type": "string", "enum": ["text", "email", "tel", "textarea"] },
        "placeholder": { "type": "string" },
        "required": { "type": "boolean" }
      }
    },
    "button": {
      "type": "object",
      "required": ["label", "path", "target", "variant", "size"],
      "properties": {
        "label": { "type": "string" },
        "path": { "type": "string" },
        "target": { "type": "string", "enum": ["_self", "_blank"] },
        "variant": { "type": "string" },
        "size": { "type": "string" }
      }
    }
  }
}

14) Load schemas in component-schema-loader.ts

After adding schemas, register them in component-schema-loader.ts:

export const componentSchemaLoader: Record<
  string,
  () => Promise<{ default: JsonSchema }>
> = {
  about: () => import("docs/schemas/About/about.schema.json"),
  hero: () => import("docs/schemas/Hero/hero1.schema.json"),
  cta: () => import("docs/schemas/cta.schema.json"),
  faq: () => import("docs/schemas/faq.schema.json"),
  testimonial: () => import("docs/schemas/testimonial.schema.json"),
  pricing: () => import("docs/schemas/pricing.schema.json"),
  partners: () => import("docs/schemas/partners.schema.json"),
  wrapper: () => import("docs/schemas/wrapper.schema.json"),
  team: () => import("docs/schemas/team.schema.json"),
  contact: () => import("docs/schemas/contact.schema.json"),
  "page-header": () => import("docs/schemas/page-header.schema.json"),
  stat: () => import("docs/schemas/stat.schema.json"),
  process: () => import("docs/schemas/process.schema.json"),
};

This enables generating forms to edit component data in the editor.


βœ… Final Result

After completing all steps above, everything is ready:

  • Component variant exists in Strapi
  • Registry is generated
  • UI package is scaffolded and built correctly
  • Component is published and promoted
  • Builder dependencies are synced
  • Editor schema loads and forms work

You can now use the component to build a new website in XSite.


βœ… Final Verification Checklist

  • cmp_variant added in Strapi
  • pnpm registry:strapi and pnpm registry:global completed
  • Component added to categories.single.json or categories.collection.json
  • Component generated via pnpm plop
  • Client/server decision verified and tsup updated if needed
  • meta.ts updated correctly
  • npx changeset created with correct bump type
  • npx changeset version applied
  • Published to staging (pnpm publish:staging ...)
  • Promoted to production (pnpm promote:production ...)
  • Dependency added to xsite-builder/package.json
  • pnpm sync:deps -- --staging and pnpm install completed
  • Schema added for new variants
  • component-schema-loader.ts updated

πŸ” Golden Rules (Do Not Skip)

  1. Never forget cmp_variant
  2. Always run registry generation after Strapi changes
  3. Always use changesets for versioning
  4. Always publish to staging first, then promote to production
  5. Always update xsite-builder/package.json dependencies
  6. Always add schema and update schema loader for new variants