Developers
This guide covers how to develop custom components that work with Agility CMS, including patterns, best practices, and common scenarios.
This guide covers how to develop custom components that work with Agility CMS, including patterns, best practices, and common scenarios.
Components are reusable UI building blocks that display content from Agility CMS. They connect your frontend code to Agility CMS content.
All Agility components follow this pattern:
import { getContentItem } from "@/lib/cms/getContentItem"
import type { UnloadedModuleProps } from "@agility/nextjs"
interface IComponentType {
heading: string
description: string
image: ImageField
}
export const ComponentName = async ({
module,
languageCode
}: UnloadedModuleProps) => {
const { fields, contentID } = await getContentItem<IComponentType>({
contentID: module.contentid,
languageCode,
})
return (
<div data-agility-component={contentID}>
<h2 data-agility-field="heading">{fields.heading}</h2>
<p data-agility-field="description">{fields.description}</p>
</div>
)
}
contentidNote: The
moduleprop name andmodule.contentidare from the SDK's legacy terminology. In Agility CMS, these are now called "components" and "component models."
All components must be registered:
// components/agility-components/index.ts
import { ComponentName } from "./ComponentName"
const allModules = [
{ name: "ComponentName", module: ComponentName },
// ... more components
]
export const getModule = (moduleName: string) => {
const obj = allModules.find(
m => m.name.toLowerCase() === moduleName.toLowerCase()
)
return obj?.module || NoComponentFound
}
Always use <AgilityPic> for Agility images:
import { AgilityPic } from "@agility/nextjs"
import type { ImageField } from "@agility/nextjs"
<AgilityPic
image={imageField}
fallbackWidth={600}
className="w-full h-auto"
data-agility-field="image"
priority={true} // For above-the-fold images
/>
Never use Next.js <Image> or plain <img> for Agility images.
interface ImageField {
url: string
label: string | null
target: string | null
filesize: number
pixelHeight: string
pixelWidth: string
height: number
width: number
}
Always use renderHTML() from Agility SDK:
import { renderHTML } from "@agility/nextjs"
<div
data-agility-field="content"
data-agility-html
className="prose dark:prose-invert"
dangerouslySetInnerHTML={renderHTML(htmlField)}
/>
Key Points:
data-agility-html attribute for inline editingprose) for stylingdark:prose-invert for dark mode supportrenderHTML() sanitizes and processes Agility HTMLconst { fields, contentID } = await getContentItem<IComponentType>({
contentID: module.contentid,
languageCode: "en-us",
})
const { items } = await getContentList<IPost>({
referenceName: "posts",
languageCode: "en-us",
take: 10,
})
For grid/link fields that reference other content:
// 1. Get parent content with nested reference
const { fields: { bentoCards: { referencename } } } =
await getContentItem<IBentoSection>({
contentID: module.contentid,
languageCode,
})
// 2. Fetch nested collection separately
const bentoCards = await getContentList<IBentoCard>({
referenceName: referencename, // Use referencename, not contentid
languageCode,
take: 20
})
Important:
referencenameDisplays content directly from component fields:
export const Hero = async ({ module, languageCode }: UnloadedModuleProps) => {
const { fields } = await getContentItem<IHero>({
contentID: module.contentid,
languageCode,
})
return (
<section data-agility-component={module.contentid}>
<h1 data-agility-field="heading">{fields.heading}</h1>
<p data-agility-field="description">{fields.description}</p>
</section>
)
}
Displays a list of content items:
export const PostListing = async ({ module, languageCode }: UnloadedModuleProps) => {
const { items } = await getContentList<IPost>({
referenceName: "posts",
languageCode,
take: 10,
})
return (
<div data-agility-component={module.contentid}>
{items.map((post) => (
<article key={post.contentID}>
<h2>{post.fields.heading}</h2>
</article>
))}
</div>
)
}
Fetches nested content:
export const BentoSection = async ({ module, languageCode }: UnloadedModuleProps) => {
// Get section with nested reference
const { fields: { bentoCards: { referencename } } } =
await getContentItem<IBentoSection>({
contentID: module.contentid,
languageCode,
})
// Fetch nested cards
const bentoCards = await getContentList<IBentoCard>({
referenceName: referencename,
languageCode,
take: 20,
})
return (
<section data-agility-component={module.contentid}>
{bentoCards.items.map((card) => (
<div key={card.contentID}>{card.fields.title}</div>
))}
</section>
)
}
priority prop for above-the-fold imagesNext: Content Fetching - Advanced content fetching patterns