π§© 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 dev2) 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: stringIf 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:globalThis updates:
- Strapi registry outputs/types
- Global registry outputs used by the platform
Generate Intefaces
Also generate interface of recently added components using:
pnpm generate:typesthis 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 plopIt 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
footerNaming 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, usecategory:name(example:hero:hero1)name: matches the component/package variant namecategory: must match the group (hero/cta/faq/etc.)type: dynamic-zone or global (as per your system)previewImage: valid URL (recommended)tags: searchable keywordsprops: default demo dataschema: RJSF JSON schemaavailability.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 changesetChoose one:
patchβ bug fixminorβ feature additionmajorβ 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 version9) Publish the component to staging (Verdaccio)
Publish to staging:
pnpm publish:staging @xsite/ui-hero110) Promote the package to production
After publishing and verifying staging, promote to production:
pnpm promote:production @xsite/ui-hero1@1.0.2This 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.jsonExample 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_variantadded in Strapi -
pnpm registry:strapiandpnpm registry:globalcompleted - Component added to
categories.single.jsonorcategories.collection.json - Component generated via
pnpm plop - Client/server decision verified and
tsupupdated if needed -
meta.tsupdated correctly -
npx changesetcreated with correct bump type -
npx changeset versionapplied - Published to staging (
pnpm publish:staging ...) - Promoted to production (
pnpm promote:production ...) - Dependency added to
xsite-builder/package.json -
pnpm sync:deps -- --stagingandpnpm installcompleted - Schema added for new variants
-
component-schema-loader.tsupdated
π Golden Rules (Do Not Skip)
- Never forget
cmp_variant - Always run registry generation after Strapi changes
- Always use changesets for versioning
- Always publish to staging first, then promote to production
- Always update
xsite-builder/package.jsondependencies - Always add schema and update schema loader for new variants