Headless CMS starter
With this starter kit you can use Contember platform as headless CMS and Next.js for frontend site.
You'll get:
- Pages with 7 blocks (Hero section, Logos section, Content section, Features section, CTA section, Testimonial section, Contact section).
- Blog and articles with powerful WYSIWYG editor.
- Messages for saving forms from the frontend website.
- Website settings (upload logotype, set up navigation).
- ACL rules for administrators and public access for frontend website.
- Basic Next.js website intentionally without any styling. See how you can easily query GraphQL API provided by Contember.
Thanks to Contember platform you can change anything. If you have any questions, we're happy to help in Discord.
How to customise the starter kit
First, you should get to know our Quickstart. You can change our starter kit as you want to. Below, you can find some examples of possible customization.
Add new page content section
Pages are build from content sections. We've prepared 7 content sections for you (Hero section, Logos section, Content section, Features section, CTA section, Testimonial section and Contact section).
Let's say you want to add new section with text and image next to each other.
1. Change the model
Go to headless-cms/api/model/ContentBlocks.ts
and add 'imageWithTextSection'
option to ContentBlockType
enum. We'll use image
and content
fields from ContentBlock
to store our data.
export const ContentBlockType = def.createEnum(
"heroSection",
"logosSection",
"contentSection",
"featureSection",
"ctaSection",
"testimonialSection",
"contactSection",
"imageWithTextSection"
)
2. Create and execute a migration
Run migration command to apply your changes.
npm run contember migrations:diff headless-cms add-image-with-text-section
Select "Yes and execute immediately" and wait for the migration to finish.
3. Add a new block to you application
Go to headless-cms/admin/components/ContentBlocks.tsx
file and add <Block />
component into <BlockRepeater />
component. <Block />
should contain all necessary fields for your new section. We want to have an image with text, so we'll add a custom component <ContentField />
(you can learn more in /headless-cms/admin/components/ContentField.tsx
) for text and <ImageUploadField />
component for the image.
export const ContentBlocks = Component(
() => (
<BlockRepeater
field="blocks"
label={undefined}
discriminationField="type"
sortableBy="order"
addButtonText="Add content block"
>
...
<Block discriminateBy="imageWithTextSection" label="Image with text section">
<ContentField />
<ImageUploadField
label={locale["Image"]}
urlField="image.url"
widthField="image.width"
heightField="image.height"
>
<TextField field="image.alt" label={locale["Alternative text"]} />
</ImageUploadField>
</Block>
),
'ContentBlocks',
)
Now you can check your new section in your application and try adding this new block to a page.
4. Add new section to next.js frontend
Now, we need to render the block on frontend.
Go to headless-cms/head/components/blocks.tsx
and add new react component called ImageWithText
which will render content and image. Next, you'll need to add your new component to block map in Blocks
component.
...
function ImageWithText({ content, image }) {
return (
<section>
{content?.parts &&
<RichTextRenderer blocks={content.parts} sourceField="json" />
}
<div>
{image &&
<div>
<img src={image.url} width={image.width} height={image.height} alt={image.alt} />
</div>
}
</div>
</section>
)
}
export default function Blocks({ blocks }) {
return blocks ? blocks.map((block) => {
const blocksElements = {
heroSection: <HeroSection {...block} key={block.id} />,
logosSection: <LogoSection {...block} key={block.id} />,
contentSection: <ContentSection {...block} key={block.id} />,
featureSection: <FeatureSection {...block} key={block.id} />,
ctaSection: <CtaSection {...block} key={block.id} />,
testimonialSection: <TestimonialSection {...block} key={block.id} />,
contactSection: <ContactSection {...block} key={block.id} />,
imageWithText: <ImageWithText {...block} key={block.id} />,
}
return blocksElements[block.type]
}) : null
}
That's all. Now you can use your new section in your website.
Reusable testimonials
You've decided that you want to edit all your testimonials in one place. You can easily do that, thanks to the Contember.
1. Change model
Create new entities called Testimonial
and TestimonailAuthor
in headless-cms/api/model/
folder.
import { SchemaDefinition as def } from "@contember/schema-definition"
import { Content } from "./Content"
import { Image } from "./Image"
export class Testimonial {
content = def.oneHasOne(Content)
author = def.oneHasOne(TestimonailAuthor)
}
export class TestimonailAuthor {
name = def.stringColumn().notNull()
title = def.stringColumn()
image = def.manyHasOne(Image)
}
Go to headless-cms/api/model/ContentBlock.ts
and remove TestimonialAuthor
entity. Replace content
and author
fields with testimonial
field.
...
import { Testimonial } from "./Testimonial"
...
export class ContentTestimonial {
order = def.intColumn().notNull()
testimonial = def.manyHasOne(Testimonial)
contentBlock = def.manyHasOne(ContentBlock, "testimonials").cascadeOnDelete()
}
...
Export new Testimonial
entities in headless-cms/api/model/index.ts
.
export * from './Article'
export * from './Button'
export * from './ContactMessage'
export * from './Content'
export * from './ContentBlock'
export * from './Image'
export * from './Link'
export * from './Menu'
export * from './Page'
export * from './Seo'
export * from './Setting'
export * from './Testimonial'
To add newly created entities, you will need to update the public
entry in the access control list.
...
const aclFactory = (model: Model.Schema): Acl.Schema => ({
roles: {
admin: {
variables: {},
stages: '*',
entities: PermissionsBuilder.create(model).allowAll().allowCustomPrimary()
.permissions,
s3: {
'**': {
upload: true,
read: true,
},
},
},
public: {
variables: {},
stages: '*',
s3: {
'**': {
upload: false,
read: true,
},
},
entities: {
...
Testimonial: {
predicates: {},
operations: readOnly(model, 'Testimonial', true),
},
...
},
},
},
})
2. Execute migrations
Run migration command to aplicate your changes.
npm run contember migrations:diff headless-cms separate-testimonials-from-content
Select "Yes and execute immediately" and wait for the migration to finish.
3. Add a page to your application
You'll need to add page where you can edit all your testimonials.
import * as React from "react"
import { CreatePage, DataGridPage, EditPage, LinkButton, TextCell, HasOne, TextField } from "@contember/admin"
import { ContentField } from '../components/ContentField'
import { ImageField } from '../components/ImageField'
export const TestimonialList = (
<DataGridPage
entities="Testimonial"
itemsPerPage={50}
rendererProps={{
title: "Testimonials",
actions: <LinkButton to="testimonialCreate">Add testimonial</LinkButton>,
}}
>
<TextCell field="author.name" header="Author's name" />
</DataGridPage>
)
export const TestimonialCreate = (
<CreatePage
entity="Testimonial"
rendererProps={{ title: "Add testinomial" }}
redirectOnSuccess="testimonialEdit(id: $entity.id)"
>
<ContentField label="Testimonial" />
<HasOne field="author">
<TextField field="name" label="Author's name" />
<ImageField field="image" label="Author's photo" />
</HasOne>
</CreatePage>
)
export const TestimonialEdit = (
<EditPage entity="Testimonial(id=$id)" rendererProps={{ title: "Edit testimonial" }}>
<ContentField label="Testimonial" />
<HasOne field="author">
<TextField field="name" label="Author's name" />
<ImageField field="image" label="Author's photo" />
</HasOne>
</EditPage>
)
Add a new page to navigation in headless-cms/admin/components/Navigation.tsx
.
export const Navigation = () => (
<Menu>
<Menu.Item>
<Menu.Item title={locale["Pages"]} to="pageList" />
<Menu.Item title={locale["Articles"]} to="articleList" />
<Menu.Item title={locale["Messages"]} to="messageList" />
<Menu.Item title={locale["Settings"]} to="settings" />
<Menu.Item title={locale["Navigation"]} to="navigationList" />
<Menu.Item title={locale["Testimonials"]} to="testimonialList" />
<Menu.Item title={locale["SEO"]}>
<Menu.Item title={locale["Articles"]} to="seoArticles" />
<Menu.Item title={locale["Pages"]} to="seoPages" />
</Menu.Item>
</Menu.Item>
</Menu>
)
Last but not least, replace <Repeater />
fields in the Testimonial Content block in headless-cms/admin/components/ContentBlocks.tsx
.
...
<Block
discriminateBy="testimonialSection"
label={<LabelWithIcon icon={<IconTestimonialSection />} label={locale["Testimonial section"]} />}
>
<TextField field="primaryText" label={locale["Headline"]} />
<ContentField />
<Repeater
field="testimonials"
label={locale["Testimonials"]}
sortableBy="order"
addButtonText="Add testimonial"
>
<SelectField field="testimonial" label={locale["Testimonial"]} options="Testimonial.author.name" />
</Repeater>
</Block>
...
That's all. Now you can edit your all your testimonals in one place.