Resources

Examples

Complete, production-ready examples showing real implementations using brease-next.


Configuration File

Central configuration for section mapping and context data.

File: src/lib/brease/config.ts

import { ComponentType } from 'react'
import { BreaseContextConfig } from 'brease-next'
import HeroSection from '@/sections/hero-section'
import SmallHeroSection from '@/sections/small-hero-section'
import TextContentSection from '@/sections/text-content-section'
import TestimonialsSection from '@/sections/testimonials-section'

type SectionProps = Record<string, unknown>

export const sectionMap: Record<string, ComponentType<SectionProps>> = {
  hero: HeroSection,
  smallHero: SmallHeroSection,
  text: TextContentSection,
  testimonials: TestimonialsSection,
}

export const contextData: BreaseContextConfig = {
  navigations: [
    { key: 'mainNavigation', id: 'nav-a01c4cbb-21f7-46d5-a89c-564307998128' },
  ],
  collections: [
    { key: 'testimonials', id: 'col-a01c8223-4e4a-40aa-90d9-70149e87322c' },
  ],
  userParams: {},
}

Cached Page Fetcher

Deduplicate page fetches across layout, metadata, and page component.

File: src/lib/brease/get-page.ts

import { cache } from 'react'
import { fetchPage } from 'brease-next'

export const getPage = cache(async (slug: string) => fetchPage(slug))

Catch-All Layout

Layout with BreaseContext wrapping the catch-all route.

File: src/app/[[...slug]]/layout.tsx

import { ReactNode } from 'react'
import { BreaseContext } from 'brease-next'
import { contextData } from '@/lib/brease/config'
import { getPage } from '@/lib/brease/get-page'
import Header from '@/components/header'
import Footer from '@/components/footer'

export default async function SlugLayout({
  children,
  params,
}: {
  children: ReactNode
  params: Promise<{ slug?: string[] }>
}) {
  const { slug } = await params
  const slugStr = (slug ?? []).join('/')

  return (
    <BreaseContext config={contextData} slug={slugStr} getPage={getPage}>
      <Header />
      <main>{children}</main>
      <Footer />
    </BreaseContext>
  )
}

Dynamic Page

Page component with static generation and metadata.

File: src/app/[[...slug]]/page.tsx

import type { Metadata } from 'next'
import {
  BreasePage,
  generateBreasePageParams,
  generateBreasePageMetadata,
} from 'brease-next'
import { sectionMap } from '@/lib/brease/config'
import { getPage } from '@/lib/brease/get-page'
import { notFound } from 'next/navigation'

export async function generateStaticParams() {
  return generateBreasePageParams()
}

export async function generateMetadata({
  params,
}: {
  params: Promise<{ slug?: string[] }>
}): Promise<Metadata> {
  const { slug } = await params
  const slugStr = (slug ?? []).join('/')
  const result = await getPage(slugStr)

  if (!result.success) return {}

  return generateBreasePageMetadata(result.data, {
    metadataBase: 'https://example.com',
  })
}

export default async function Page({
  params,
}: {
  params: Promise<{ slug?: string[] }>
}) {
  const { slug } = await params
  const slugStr = (slug ?? []).join('/')
  const result = await getPage(slugStr)

  if (!result.success) {
    if (result.status === 404) return notFound()
    throw new Error(`Failed to load page: ${result.error}`)
  }

  return <BreasePage page={result.data} sectionMap={sectionMap} />
}

Root Layout

Root layout with site-level metadata.

File: src/app/layout.tsx

import { ReactNode } from 'react'
import type { Metadata } from 'next'
import { fetchSite } from 'brease-next'
import 'brease-next/styles'
import './globals.css'

export async function generateMetadata(): Promise<Metadata> {
  const result = await fetchSite()

  if (result.success) {
    return {
      title: {
        template: `%s | ${result.data.name}`,
        default: result.data.name,
      },
    }
  }

  return { title: { template: '%s', default: 'My Site' } }
}

export default function RootLayout({ children }: { children: ReactNode }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}

Section Components

Hero Section

Full-featured hero section with responsive image.

File: src/sections/hero-section.tsx

'use client'

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

interface HeroSectionProps {
  title: string
  body: string
  heroMedia: BreaseMedia
}

export default function HeroSection({
  title,
  body,
  heroMedia,
}: HeroSectionProps) {
  return (
    <section className="relative py-24 sm:py-32">
      <div className="mx-auto max-w-7xl px-6 lg:flex lg:items-center lg:gap-x-10 lg:px-8">
        <div className="mx-auto max-w-2xl lg:mx-0 lg:flex-auto">
          <h1 className="text-5xl font-semibold tracking-tight sm:text-7xl">
            {title}
          </h1>
          <div
            className="mt-8 text-lg text-gray-500"
            dangerouslySetInnerHTML={{ __html: body }}
          />
        </div>
        <div className="mt-16 lg:mt-0 lg:shrink-0 lg:grow">
          {heroMedia && <BreaseImage breaseImage={heroMedia} priority />}
        </div>
      </div>
    </section>
  )
}

Testimonials Section

Section that displays collection data passed via a collection-type element.

File: src/sections/testimonials-section.tsx

'use client'

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

interface TestimonialsSectionProps {
  title: string
  testimonials: BreaseCollection
}

export default function TestimonialsSection({
  title,
  testimonials,
}: TestimonialsSectionProps) {
  return (
    <section className="py-24 sm:py-32">
      <div className="mx-auto max-w-7xl px-6 lg:px-8">
        <h2 className="text-4xl font-semibold tracking-tight text-center">
          {title}
        </h2>
        <div className="mt-16 grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3">
          {testimonials.entries.map((entry) => {
            const elements = entry.elements as Record<string, unknown>
            const avatar = elements.avatar as BreaseMedia | undefined

            return (
              <blockquote key={entry.uuid} className="rounded-2xl border p-8">
                <p className="text-gray-700">
                  &ldquo;{elements.quote as string}&rdquo;
                </p>
                <footer className="mt-6 flex items-center gap-4">
                  {avatar && (
                    <BreaseImage
                      breaseImage={avatar}
                      variant="sm"
                      className="h-12 w-12 rounded-full object-cover"
                    />
                  )}
                  <div>
                    <p className="font-semibold">{elements.author as string}</p>
                    <p className="text-sm text-gray-500">
                      {elements.role as string}
                    </p>
                  </div>
                </footer>
              </blockquote>
            )
          })}
        </div>
      </div>
    </section>
  )
}

Responsive navigation using the useBrease hook.

File: src/components/header.tsx

'use client'

import Link from 'next/link'
import { useState } from 'react'
import { useBrease, BreaseLink } from 'brease-next'

export default function Header() {
  const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
  const { navigations } = useBrease()
  const mainNav = navigations.mainNavigation

  if (!mainNav) return null

  return (
    <header className="absolute inset-x-0 top-0 z-50">
      <nav className="flex items-center justify-between p-6 lg:px-8">
        <Link href="/" className="text-xl font-bold">
          Logo
        </Link>

        {/* Desktop navigation */}
        <div className="hidden lg:flex lg:gap-x-8">
          {mainNav.items.map((item) => (
            <BreaseLink
              key={item.uuid}
              linkData={item}
              className="text-sm font-semibold"
            >
              {item.label}
            </BreaseLink>
          ))}
        </div>

        {/* Mobile toggle */}
        <button
          type="button"
          onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
          className="lg:hidden"
        >
          Menu
        </button>
      </nav>

      {/* Mobile menu */}
      {mobileMenuOpen && (
        <div className="fixed inset-0 z-50 bg-white p-6 lg:hidden">
          <button onClick={() => setMobileMenuOpen(false)}>Close</button>
          <div className="mt-6 space-y-2">
            {mainNav.items.map((item) => (
              <BreaseLink
                key={item.uuid}
                linkData={item}
                className="block rounded-lg px-3 py-2 text-base font-semibold"
              >
                {item.label}
              </BreaseLink>
            ))}
          </div>
        </div>
      )}
    </header>
  )
}

Next.js Configuration

Complete configuration with redirects, images, and CORS.

File: next.config.ts

import type { NextConfig } from 'next'
import { fetchRedirects } from 'brease-next/server'

const nextConfig: NextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'assets.brease.io',
        pathname: '/**',
      },
    ],
  },
  async redirects() {
    const result = await fetchRedirects()

    if (!result.success) {
      console.error('Failed to fetch redirects:', result.error)
      return []
    }

    return result.data.map((redirect) => ({
      source: redirect.source,
      destination: redirect.destination,
      permanent: redirect.type === '301' || redirect.type === '308',
    }))
  },
  async headers() {
    return [
      {
        source: '/:path*',
        headers: [
          {
            key: 'Content-Security-Policy',
            value: "frame-ancestors 'self' https://app.brease.io https://dev.brease.io",
          },
        ],
      },
    ]
  },
}

export default nextConfig

SEO Configuration

File: src/app/robots.ts

import type { MetadataRoute } from 'next'
import { generateBreaseRobots } from 'brease-next'

export default function robots(): MetadataRoute.Robots {
  return generateBreaseRobots('https://example.com')
}

File: src/app/sitemap.ts

import type { MetadataRoute } from 'next'
import { generateSitemap } from 'brease-next'

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const result = await generateSitemap()
  if (!result.success) return []
  return result.data
}

Next Steps

For common issues and debugging, see Troubleshooting. For more patterns, browse the Implementation Patterns section.

Previous
Deployment