Tutorial

How to Build a Landing Page

Build a landing page with Cmssy: define hero, features and CTA blocks with the current SDK in your own Next.js app, then compose and publish the page in the visual editor.

C
Cmssy Team
6 min read

How to Build a Landing Page with Cmssy and Next.js

Step by step: define hero, features and CTA blocks with the current Cmssy SDK in your own Next.js app, then build and publish the page in the visual editor.

What Makes a Landing Page Work

Before you build, it helps to know what a high-converting landing page needs:

  • A clear value proposition - visitors should understand what you offer within seconds.
  • One strong call to action - a single primary CTA that stands out and says exactly what to do.
  • Social proof - testimonials, customer logos or numbers that build trust.
  • A clean design - every element supports the conversion goal.
  • Fast load times - speed matters for visitors and for search rankings.

How Cmssy Works

Cmssy is a headless CMS with a visual editor. You write the blocks as React components in your own app with @cmssy/react and @cmssy/next, and you deploy that app yourself, on Vercel or anywhere else. Cmssy stores and delivers the content; your app renders it. The visual editor frames your running site, so editors drag blocks and edit text on the real page.

Step 1: Start From an Example or Wire Your Own App

The fastest start is a runnable example. cmssy-io/examples holds standalone apps that render the public demo workspace without an account:

git clone https://github.com/cmssy-io/examples
cd examples/next-storefront
pnpm install
cp .env.example .env.local
pnpm dev

next-storefront already ships the three blocks a landing page needs - hero, value-props and cta-banner - so you can read finished versions of everything below.

To wire an app of your own, let the CLI copy the files in and connect it to your workspace:

npx create-next-app@latest my-site
cd my-site
npx @cmssy/cli@latest init
pnpm install
npx @cmssy/cli@latest link --token cs_...
pnpm dev

init writes a block registry in cmssy/blocks.ts, an example block in blocks/hero/, and a catch-all page that renders whatever you publish with createCmssyPage from @cmssy/next/server. Every file is plain code in your repository. link writes your workspace's slugs and draft secret into .env.local and checks them against the API.

Step 2: Define Your Blocks

A block is a React component plus a schema the editor reads. You declare the editable fields with fields, type the component with BlockProps, and register both with defineBlock. Replace the example hero with one that has a call to action.

blocks/hero/Hero.tsx:

import { fields, type BlockProps } from "@cmssy/react";

export const heroProps = {
  heading: fields.text({ label: "Heading", required: true }),
  subheading: fields.textarea({ label: "Subheading" }),
  ctaText: fields.text({ label: "Button text" }),
  ctaUrl: fields.link({ label: "Button URL" }),
};

export default function Hero({ content }: BlockProps<typeof heroProps>) {
  const { heading, subheading, ctaText, ctaUrl } = content;
  return (
    <section>
      <h1>{heading}</h1>
      {subheading ? <p>{subheading}</p> : null}
      {ctaText && ctaUrl ? <a href={ctaUrl}>{ctaText}</a> : null}
    </section>
  );
}

blocks/hero/block.ts:

import { defineBlock } from "@cmssy/react";
import Hero, { heroProps } from "./Hero";

export const heroBlock = defineBlock({
  type: "hero",
  label: "Hero",
  component: Hero,
  props: heroProps,
});

The component receives everything in one content prop, typed from the schema: heading is a string because it is required, the other fields may be undefined. A list of features is a repeater, and each item has its own schema.

blocks/features/Features.tsx:

import { fields, type BlockProps } from "@cmssy/react";

export const featuresProps = {
  heading: fields.text({ label: "Heading" }),
  items: fields.repeater({
    label: "Features",
    itemLabel: "Feature",
    maxItems: 6,
    itemSchema: {
      title: fields.text({ label: "Title", required: true }),
      text: fields.textarea({ label: "Text" }),
    },
  }),
};

export default function Features({ content }: BlockProps<typeof featuresProps>) {
  const items = content.items ?? [];
  return (
    <section>
      {content.heading ? <h2>{content.heading}</h2> : null}
      <ul>
        {items.map((item, index) => (
          <li key={index}>
            <strong>{item.title}</strong>
            {item.text ? <p>{item.text}</p> : null}
          </li>
        ))}
      </ul>
    </section>
  );
}

blocks/features/block.ts is the same defineBlock call as the hero's, with type: "features" and component: Features. Write the CTA block the same way - a heading, a text, a button text and a fields.link - or copy cta-banner from next-storefront, which is exactly that. Then list all three in the registry.

cmssy/blocks.ts:

import { heroBlock } from "@/blocks/hero/block";
import { featuresBlock } from "@/blocks/features/block";
import { ctaBlock } from "@/blocks/cta/block";

export const blocks = [heroBlock, featuresBlock, ctaBlock];

That array is the single list of blocks your site can render. The server renders from it, and the editor reads each block's schema from it, so the blocks appear in the editor's picker with the fields you declared. You own the markup and the styling, so the blocks match your design system exactly.

Step 3: Create the Page in Cmssy

In the Cmssy admin, create a new page and give it a name and a URL slug, for example /launch. The slug is the path your app will render. The page starts empty - you fill it with your blocks next.

Step 4: Build the Page in the Visual Editor

Open the page in the editor. While you develop, point the editor's Dev host at http://localhost:3000; for the deployed site, set the workspace Preview URL. The editor frames your running app, so you see your real components. Drag the Hero, Features and CTA blocks onto the page in that order and click any block to edit it - the fields you declared show up as form controls. Write a headline, add three or four benefits, and set the button text and link.

Step 5: Add Social Proof

People trust other people. If you have a testimonials, logos or stats block (next-storefront ships a stats-band), drag it in right after the hero to build credibility before you ask for the conversion.

Step 6: Let an AI Agent Do the Typing (Optional)

The admin has a built-in assistant (Cmd + K) that runs on a model key you bring, and the same tools are available to Claude and other MCP clients through the MCP server. Both are on every plan. With the page open in the editor, the assistant can generate the page, translate it, or edit the blocks in front of you; it can also write the SEO title and description. It edits content only, never your code, and the canvas edits stay unsaved until you save. Publishing is one of the steps that waits for your confirmation.

Step 7: Configure SEO

Each page has an SEO title and a meta description per language, plus a list of keywords:

  • SEO title - include your main keyword and keep it under 60 characters.
  • Meta description - summarise the page in about 155 characters, with a call to action.
  • Keywords - terms that match what your audience searches for.

The delivery API returns them with the page, and your app puts them into the Next.js metadata in generateMetadata. The next-storefront example does it in services/seo.ts.

Step 8: Publish

Click Publish. The content goes live through the delivery API, and your app renders it at the slug you set, on your own domain and hosting. Point a content.changed webhook at your app's /api/revalidate route (the CLI writes that route for you) and a publish shows up without a redeploy.

Landing Page Best Practices

  • One goal per page - every landing page should have a single conversion goal.
  • Mobile first - test on small screens before anything else.
  • Test your headlines - small headline changes can move conversion noticeably.
  • Watch load time - compress images and drop scripts the page does not need.
  • Use urgency honestly - a real deadline helps; a fake countdown costs trust.

Next Steps