Atlas CMS
Use Cases

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:

  1. faq_category (Fields: name, icon)
  2. faq (Fields: question, answer, category - a relation to faq_category or 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.

lib/atlas.ts
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.

app/help/page.tsx
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.

On this page