Help Center
Build a categorized Help Center by combining multiple content types using the Atlas CMS Public API.
FAQ & Help Center
A Help Center typically consists of "Categories" (e.g., Billing, Technical Support) and "FAQs" belonging to those categories. This demonstrates how to query entries and group them efficiently.
Schema Setup
Assume you have two content types:
faq_category(Fields:name,icon)faq(Fields:question,answer,category- a relation tofaq_categoryor a matching string slug).
What We're Building
We will fetch all FAQs, sort them by category, and render them in an accordion-style layout.
1. API Helper
Instead of making multiple requests, we can fetch all FAQs (or up to a high limit) and group them in memory.
const BASE = process.env.ATLAS_BASE_URL + '/api/v1/public'
const headers = { 'X-API-Key': process.env.ATLAS_API_KEY! }
export interface FAQ {
slug: string
data: {
question: string
answer: string
category_slug: string // E.g. 'billing'
}
}
export async function getAllFAQs(): Promise<FAQ[]> {
const res = await fetch(`${BASE}/entries?type=faq&limit=100`, {
headers,
next: { revalidate: 3600 }, // Cache for 1 hour
})
if (!res.ok) return []
const { data } = await res.json()
return data
}2. Help Center Page
We will group the FAQs by category_slug using standard JavaScript array methods.
import { getAllFAQs } from '@/lib/atlas'
export default async function HelpCenterPage() {
const faqs = await getAllFAQs()
// Group FAQs by category_slug
const groupedFAQs = faqs.reduce((acc, faq) => {
const cat = faq.data.category_slug || 'General'
if (!acc[cat]) acc[cat] = []
acc[cat].push(faq)
return acc
}, {} as Record<string, typeof faqs>)
return (
<main className="container py-16 max-w-4xl">
<h1 className="text-4xl font-extrabold mb-4 text-center">Help Center</h1>
<p className="text-lg text-muted text-center mb-12">
Find answers to frequently asked questions.
</p>
<div className="space-y-12">
{Object.entries(groupedFAQs).map(([category, items]) => (
<section key={category}>
<h2 className="text-2xl font-bold mb-6 capitalize border-b pb-2">
{category.replace('-', ' ')}
</h2>
<div className="space-y-4">
{items.map((faq) => (
<details
key={faq.slug}
className="group border rounded-lg p-4 bg-card [&_summary::-webkit-details-marker]:hidden"
>
<summary className="flex items-center justify-between cursor-pointer font-semibold text-lg">
{faq.data.question}
<span className="transition group-open:rotate-180">
<svg fill="none" height="24" shapeRendering="geometricPrecision" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.5" viewBox="0 0 24 24" width="24"><path d="M6 9l6 6 6-6"></path></svg>
</span>
</summary>
<p className="text-muted-foreground mt-4 leading-relaxed">
{faq.data.answer}
</p>
</details>
))}
</div>
</section>
))}
</div>
</main>
)
}Summary
This approach minimizes API calls by fetching all FAQs at once and caching them aggressively (revalidate: 3600). Because FAQs rarely change every minute, this provides an instant, snappy experience for your users.