Documentation

Components

The brease-next package provides React components for rendering CMS content.

Overview

The package includes three primary components:

  1. BreasePage - Main page renderer that maps sections to components
  2. BreaseImage - Optimized image component for Brease media
  3. BreaseContext - Server component that provides global data via context

And one client-side hook:

  • useBrease() - Hook to access context data from client components

BreasePage

The main component for rendering Brease pages with dynamic sections.

Import

import { BreasePage } from 'brease-next';

Props

interface BreasePageProps {
  page: BreasePageType;
  sectionMap: Record<string, React.ComponentType<Record<string, unknown>>>;
}

Usage

import { fetchPage, BreasePage } from 'brease-next';
import { sectionMap } from '@/lib/brease-config';
import { notFound } from 'next/navigation';

export default async function Page() {
  const result = await fetchPage('/about');

  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} />;
}

How It Works

  1. Iterates through page.sections array
  2. For each section, looks up component from sectionMap using section.type
  3. Spreads section.elements as props to the matched component
  4. Renders the component with a unique key (section.uuid)

Section Mapping

The sectionMap connects Brease section types to React components:

// src/lib/brease-config.ts
import { ComponentType } from 'react';
import HeroSection from '@/sections/hero-section';
import FeaturesSection from '@/sections/features-section';
import TestimonialsSection from '@/sections/testimonials-section';

type SectionProps = Record<string, unknown>;

export const sectionMap: Record<string, ComponentType<SectionProps>> = {
  hero: HeroSection,
  features: FeaturesSection,
  testimonials: TestimonialsSection,
};

Error Handling

If a section type is not found in the map:

  • A warning is logged to the console: No component found for section type: {type}
  • The section is skipped (returns null)
  • Other sections continue to render normally

BreaseImage

Optimized image component that wraps Next.js Image with Brease media data.

Import

import { BreaseImage } from 'brease-next';

Props

interface BreaseImageProps {
  breaseImage: BreaseMedia;
  className?: string;
}

Usage

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

interface HeroSectionProps {
  heroMedia: BreaseMedia;
}

export default function HeroSection({ heroMedia }: HeroSectionProps) {
  return (
    <div>
      <h1>Welcome</h1>
      <BreaseImage
        breaseImage={heroMedia}
        className="rounded-lg shadow-xl"
      />
    </div>
  );
}

Features

  • Wraps Next.js Image component with optimizations
  • Automatically extracts image properties from BreaseMedia object
  • Provides fallback alt text if not specified
  • Handles null/undefined images gracefully

Generated Props

The component automatically maps BreaseMedia properties to Next.js Image props:

<Image
  src={breaseImage.path}
  alt={breaseImage.alt || breaseImage.name || 'Image alt.'}
  width={breaseImage.width}
  height={breaseImage.height}
  className={className}
/>

Null Handling

The component returns null if breaseImage is falsy, preventing errors when media is optional in your sections.


BreaseContext

Server component that fetches global data and provides it via React Context using a configuration-driven approach.

Import

import { BreaseContext } from 'brease-next';

Props

interface BreaseContextProps {
  config: {
    navigations?: Array<{ key: string; id: string }>;
    collections?: Array<{ key: string; id: string }>;
  };
  children: React.ReactNode;
}

Setup

Use BreaseContext in your root layout with configuration from brease-config.ts:

// src/app/layout.tsx
import { BreaseContext } from 'brease-next';
import { contextData } from '@/lib/brease-config';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <BreaseContext config={contextData}>
          {children}
        </BreaseContext>
      </body>
    </html>
  );
}

Configuration

Define your navigations and collections in brease-config.ts:

// src/lib/brease-config.ts
export const contextData = {
  navigations: [
    { key: 'mainNavigation', id: 'nav-a01c4cbb-21f7-46d5-a89c-564307998128' },
    { key: 'footerNavigation', id: 'nav-xyz...' },
  ],
  collections: [
    { key: 'news', id: 'col-a01c8223-4e4a-40aa-90d9-70149e87322c' },
    { key: 'products', id: 'col-abc...' },
  ],
};

Finding Your UUIDs:

  1. Open Brease dashboard
  2. Navigate to Navigations or Collections section
  3. Select your resource
  4. Copy the UUID from the settings or URL

What It Does

  1. Fetches all configured navigations and collections server-side
  2. Organizes data by the keys you defined in config
  3. Makes data available to all child components via the useBrease() hook

Configuration Examples

Single Navigation:

export const contextData = {
  navigations: [
    { key: 'mainNavigation', id: 'nav-123...' },
  ],
  collections: [],
};

Multiple Navigations:

export const contextData = {
  navigations: [
    { key: 'mainNavigation', id: 'nav-header-uuid' },
    { key: 'footerNavigation', id: 'nav-footer-uuid' },
    { key: 'sidebarNavigation', id: 'nav-sidebar-uuid' },
  ],
  collections: [],
};

Navigations + Collections:

export const contextData = {
  navigations: [
    { key: 'mainNavigation', id: 'nav-123...' },
  ],
  collections: [
    { key: 'news', id: 'col-news-uuid' },
    { key: 'products', id: 'col-products-uuid' },
    { key: 'testimonials', id: 'col-testimonials-uuid' },
  ],
};

Error Handling

The component handles fetch errors gracefully:

  • Logs errors to console for debugging
  • Continues rendering with undefined data for failed resources
  • Your components should check for undefined data when using useBrease()

useBrease Hook

Client-side hook to access data provided by BreaseContext.

Import

import { useBrease } from 'brease-next';

Return Value

The hook returns an object with navigations and collections properties, where each is an object containing your configured resources by key:

{
  navigations: {
    [key: string]: BreaseNavigation | undefined;
  };
  collections: {
    [key: string]: BreaseCollectionEntry[] | undefined;
  };
}

Usage

'use client';

import { useBrease } from 'brease-next';
import Link from 'next/link';

export default function Navigation() {
  const { navigations } = useBrease();
  const { mainNavigation } = navigations;

  if (!mainNavigation) {
    return null;
  }

  return (
    <nav>
      <ul>
        {mainNavigation.items.map((item, index) => (
          <li key={index}>
            {item.type === 'internal' && item.target ? (
              <Link href={item.target.slug}>{item.value}</Link>
            ) : (
              <a href={item.url} target="_blank" rel="noopener noreferrer">
                {item.value}
              </a>
            )}
          </li>
        ))}
      </ul>
    </nav>
  );
}

Accessing Collections

'use client';

import { useBrease } from 'brease-next';

export default function LatestNews() {
  const { collections } = useBrease();
  const { news } = collections;

  if (!news || news.length === 0) {
    return null;
  }

  return (
    <div>
      <h2>Latest News</h2>
      {news.slice(0, 3).map((entry) => (
        <article key={entry.slug}>
          <h3>{entry.elements.title as string}</h3>
          <p>{entry.elements.excerpt as string}</p>
        </article>
      ))}
    </div>
  );
}

Error Handling

The hook throws an error if used outside of BreaseContext:

if (context === undefined) {
  throw new Error('useBrease must be used within a BreaseContext');
}

Client Component Requirement

This hook must be used in client components only (marked with 'use client').


Complete Integration Example

Here's how all components work together:

// src/lib/brease-config.ts
import HeroSection from '@/sections/hero-section';
import TextSection from '@/sections/text-section';

export const sectionMap = {
  hero: HeroSection,
  text: TextSection,
};

export const contextData = {
  navigations: [
    { key: 'mainNavigation', id: 'nav-123...' },
  ],
  collections: [
    { key: 'news', id: 'col-456...' },
  ],
};

// src/app/layout.tsx (Server Component)
import { BreaseContext } from 'brease-next';
import { contextData } from '@/lib/brease-config';

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <BreaseContext config={contextData}>
          {children}
        </BreaseContext>
      </body>
    </html>
  );
}

// src/app/page.tsx (Server Component)
import { fetchPage, BreasePage } from 'brease-next';
import { sectionMap } from '@/lib/brease-config';

export default async function Home() {
  const result = await fetchPage('/');

  if (!result.success) {
    throw new Error(`Failed to load page: ${result.error}`);
  }

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

// src/sections/hero-section.tsx (Client Component)
'use client';

import { BreaseImage, type BreaseMedia } from 'brease-next';
import Navigation from '@/components/navigation';

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

export default function HeroSection({ title, body, heroMedia }: HeroSectionProps) {
  return (
    <div>
      <Navigation />
      <h1>{title}</h1>
      <div dangerouslySetInnerHTML={{ __html: body }} />
      <BreaseImage breaseImage={heroMedia} className="hero-image" />
    </div>
  );
}

// src/components/navigation.tsx (Client Component)
'use client';

import { useBrease } from 'brease-next';
import Link from 'next/link';

export default function Navigation() {
  const { navigations } = useBrease();
  const { mainNavigation } = navigations;

  if (!mainNavigation) return null;

  return (
    <nav>
      {mainNavigation.items.map((item, i) => (
        <Link key={i} href={item.target?.slug || item.url || '#'}>
          {item.value}
        </Link>
      ))}
    </nav>
  );
}

Best Practices

Section Components

  1. Type your props explicitly:
interface HeroSectionProps {
  title: string;
  subtitle: string;
  heroMedia: BreaseMedia;
}
  1. Handle optional fields:
export default function HeroSection({ title, subtitle, heroMedia }: HeroSectionProps) {
  return (
    <div>
      <h1>{title}</h1>
      {subtitle && <h2>{subtitle}</h2>}
      {heroMedia && <BreaseImage breaseImage={heroMedia} />}
    </div>
  );
}
  1. Use client directive only when needed:
  • Use 'use client' if component uses hooks, event handlers, or browser APIs
  • Keep server components when possible for better performance

Image Optimization

  1. Add appropriate sizing:
<BreaseImage
  breaseImage={heroMedia}
  className="w-full h-auto max-w-4xl"
/>
  1. Use priority for above-fold images: Extend BreaseImage or use Next.js Image directly for priority loading.

Context Usage

  1. Check for undefined data:
const { navigations } = useBrease();
const { mainNavigation } = navigations;

if (!mainNavigation) {
  return <div>Loading navigation...</div>;
}
  1. Configure all resources upfront: Define all navigations and collections in contextData for centralized management.

Next Steps

Now that you understand the components, follow the Implementation Guide for step-by-step instructions on building pages.

Previous
API Reference