Implementation Patterns

Collection Patterns

Collections in Brease represent structured, repeatable data — FAQs, team members, testimonials, partner logos, feature lists, pricing tiers, and similar content that shares the same schema across multiple entries. Collections are not pages — if content needs its own URL (like blog posts), it should be a page with a page type instead.


Accessing Collections

For collections needed across your site (e.g., in a sidebar, footer, or shared component), configure them in BreaseContextConfig:

1. Register collections in config

// src/lib/brease/config.ts
import { BreaseContextConfig } from 'brease-next'

export const contextData: BreaseContextConfig = {
  navigations: [{ key: 'header', id: 'your-header-nav-uuid' }],
  collections: [
    { key: 'faqs', id: 'your-faq-collection-uuid' },
    { key: 'team', id: 'your-team-collection-uuid' },
  ],
  userParams: {},
}

Collections are also retrived from page content but they are page-scoped in this way.

2. Access in client components

'use client'

import { useBrease } from 'brease-next'

export default function FAQSection() {
  const { collections } = useBrease()
  const faqs = collections?.faqs

  if (!faqs || faqs.entries.length === 0) return null

  return (
    <section className="py-16">
      <h2 className="mb-8 text-3xl font-bold">Frequently Asked Questions</h2>
      <dl className="space-y-6">
        {faqs.entries.map((entry) => (
          <div key={entry.uuid}>
            <dt className="text-lg font-semibold">
              {entry.elements.question as string}
            </dt>
            <dd className="mt-2 text-gray-600">
              {entry.elements.answer as string}
            </dd>
          </div>
        ))}
      </dl>
    </section>
  )
}

Context collections are locale-aware

BreaseContext automatically fetches collections using the locale derived from the current page slug. You do not need to pass locale manually when using the context pattern.


Typing Collection Entry Elements

Collection entry elements are typed as Record<string, unknown> by default. Define an interface matching your CMS schema and cast the elements for type safety:

import type { BreaseMedia } from 'brease-next'

interface TeamMemberElements {
  name: string
  role: string
  bio: string // Rich text HTML
  photo: BreaseMedia
  linkedIn: string
}

const el = entry.elements as unknown as TeamMemberElements

This gives you full autocomplete and type checking when rendering entry data.


Fetching Collections Directly

When you need collection data outside of BreaseContext (e.g., in a server component or a route that doesn't use the catch-all layout), fetch directly:

import { fetchCollectionById } from 'brease-next/server'

const TEAM_COLLECTION_ID = 'your-team-collection-uuid'

export default async function TeamPage() {
  const result = await fetchCollectionById(TEAM_COLLECTION_ID, 'en')

  if (!result.success) {
    return <p>Failed to load team members.</p>
  }

  return (
    <div className="grid gap-8 md:grid-cols-3">
      {result.data.entries.map((entry) => {
        const el = entry.elements as unknown as TeamMemberElements
        return (
          <div key={entry.uuid} className="text-center">
            <h3 className="text-lg font-semibold">{el.name}</h3>
            <p className="text-sm text-gray-500">{el.role}</p>
          </div>
        )
      })}
    </div>
  )
}

Locale parameter required

fetchCollectionById requires a locale parameter. When fetching directly (outside BreaseContext), you must supply it explicitly.


Fetching a Single Entry

Use fetchEntryById when you need a specific entry by its UUID:

import { fetchEntryById } from 'brease-next/server'

const result = await fetchEntryById(
  'your-collection-uuid',
  'entry-uuid-here',
  'en',
)

if (result.success) {
  const entry = result.data
  console.log(entry.uuid, entry.name)
  console.log(entry.elements)
}

Example: Testimonials Section

A complete example rendering a testimonials collection with images:

'use client'

import { useBrease, BreaseImage, type BreaseMedia } from 'brease-next'

interface TestimonialElements {
  quote: string
  author: string
  role: string
  avatar: BreaseMedia
}

export default function Testimonials() {
  const { collections } = useBrease()
  const testimonials = collections?.testimonials

  if (!testimonials || testimonials.entries.length === 0) return null

  return (
    <section className="py-24">
      <h2 className="mb-16 text-center text-3xl font-bold">
        What Our Clients Say
      </h2>
      <div className="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
        {testimonials.entries.map((entry) => {
          const el = entry.elements as unknown as TestimonialElements
          return (
            <blockquote key={entry.uuid} className="rounded-xl border p-6">
              <p className="text-gray-700">&ldquo;{el.quote}&rdquo;</p>
              <footer className="mt-4 flex items-center gap-3">
                {el.avatar && (
                  <BreaseImage
                    breaseImage={el.avatar}
                    variant="sm"
                    className="h-10 w-10 rounded-full object-cover"
                  />
                )}
                <div>
                  <p className="font-semibold">{el.author}</p>
                  <p className="text-sm text-gray-500">{el.role}</p>
                </div>
              </footer>
            </blockquote>
          )
        })}
      </div>
    </section>
  )
}

Multi-Locale Collections

When your site supports multiple locales, pass the locale to collection fetch functions:

import { fetchCollectionById, fetchEntryById } from 'brease-next/server'

// Fetch the Slovak version of a collection
const result = await fetchCollectionById('your-collection-uuid', 'sk')

// Fetch a specific entry in Slovak
const entry = await fetchEntryById('your-collection-uuid', 'entry-uuid', 'sk')

When using BreaseContext, locale is handled automatically based on the page slug.


Collections vs Pages

When to use which

Collections are for structured data that lives within a page — FAQs, team members, testimonials, feature lists, pricing tiers, partner logos. Entries do not have their own URL.

Pages (with page types) are for content that needs its own URL — blog posts, case studies, product pages. Use a page type with a template to standardize their structure, and reference fields for page-level metadata like author or publish date. See Templates & Page Types.


Best Practices

  • Type your elements. Always create an interface for your entry elements and cast entry.elements to it. This catches typos and gives you IDE autocomplete.
  • Handle missing data gracefully. Elements can be null or undefined if not filled in by the content editor. Use optional chaining and fallbacks.
  • Use context for global lists. If you need collection data on every page (e.g., a testimonials widget in the sidebar), configure it in contextData so it is fetched once by BreaseContext.
  • Use direct fetches for isolated pages. For pages that only need collection data in specific places, use fetchCollectionById directly in server components.

Next Steps

Previous
Pages & Dynamic Routes