Documentation
Implementation Guide
Step-by-step guide to implementing Brease CMS in your Next.js application using the brease-next package.
Prerequisites
Before starting, ensure you have:
- Completed Getting Started
- Reviewed API Reference and Components
Step 1: Set Up Root Layout
The root layout wraps your app with BreaseContext and provides global data.
Create src/app/layout.tsx
import type { Metadata } from 'next';
import { BreaseContext, fetchSite } from 'brease-next';
import { contextData } from '@/lib/brease-config';
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,
},
description: `${result.data.name} - ${result.data.domain}`,
};
}
return {
title: 'My Site',
description: 'Site description',
};
}
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body>
<BreaseContext config={contextData}>
{children}
</BreaseContext>
</body>
</html>
);
}
What This Does
- Fetches site data to populate metadata
- Creates a template for page titles (
Page Title | Site Name) - Wraps application with
BreaseContextusing your configuration - Provides consistent HTML structure
Step 2: Create Homepage
The homepage is typically at the root slug (/).
Create src/app/page.tsx
import type { Metadata } from 'next';
import { fetchPage, fetchSite, BreasePage, generateBreasePageMetadata } from 'brease-next';
import { sectionMap } from '@/lib/brease-config';
import { notFound } from 'next/navigation';
export const dynamic = 'force-static';
export async function generateMetadata(): Promise<Metadata> {
const result = await fetchSite();
const metadata = await generateBreasePageMetadata('/');
if (result.success) {
metadata.title = `${metadata.title} | ${result.data.name}`;
}
return metadata;
}
export default async function Home() {
const result = await fetchPage('/');
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} />;
}
Key Points
dynamic = 'force-static'ensures the page is statically generatedgenerateMetadata()creates SEO tags from Brease data- Error handling returns 404 or throws error for error boundary
BreasePagerenders all sections dynamically
Step 3: Create Dynamic Pages Route
Handle all other pages with a dynamic route.
Create src/app/[subpageSlug]/page.tsx
import type { Metadata } from 'next';
import { fetchPage, generateBreasePageParams, generateBreasePageMetadata, BreasePage } from 'brease-next';
import { sectionMap } from '@/lib/brease-config';
import { notFound } from 'next/navigation';
export const dynamic = 'auto';
export async function generateStaticParams() {
return generateBreasePageParams();
}
export async function generateMetadata({ params }: { params: Promise<{ subpageSlug: string }> }): Promise<Metadata> {
const { subpageSlug } = await params;
return generateBreasePageMetadata(`/${subpageSlug}`);
}
export default async function Subpage({ params }: { params: Promise<{ subpageSlug: string }> }) {
const { subpageSlug } = await params;
const result = await fetchPage(`/${subpageSlug}`);
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 Static Generation Works
- At build time,
generateStaticParams()fetches all pages from Brease - Next.js pre-renders each page
- At runtime, if a page doesn't exist, returns 404
- Use
dynamic = 'auto'to allow ISR (Incremental Static Regeneration)
Step 4: Create Section Components
Section components receive data from Brease sections and render the content.
Example: Hero Section
// src/sections/hero-section.tsx
'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 className="bg-white dark:bg-gray-900">
<Navigation />
<div className="relative isolate pt-14">
<div className="mx-auto max-w-7xl px-6 py-24">
<div className="mx-auto max-w-2xl">
<h1 className="text-5xl font-semibold">
{title}
</h1>
<div
className="mt-8 text-lg"
dangerouslySetInnerHTML={{ __html: body }}
/>
</div>
<div className="mt-16">
<BreaseImage breaseImage={heroMedia} />
</div>
</div>
</div>
</div>
);
}
Section Prop Types
Define props based on your Brease section schema:
interface TextSectionProps {
heading: string;
content: string;
alignment?: string;
}
interface ImageGridSectionProps {
images: BreaseMedia[];
columns: number;
}
interface CtaSectionProps {
title: string;
buttonText: string;
buttonLink: string;
backgroundColor?: string;
}
Handling Rich Text
Use dangerouslySetInnerHTML for HTML content from Brease:
<div dangerouslySetInnerHTML={{ __html: body }} />
Note: Ensure content is sanitized in Brease or add a sanitization library.
Step 5: Map Sections and Configure Context
Connect Brease section types to your React components and configure global data.
Update src/lib/brease-config.ts
import { ComponentType } from 'react';
import HeroSection from '@/sections/hero-section';
import SmallHeroSection from '@/sections/small-hero-section';
import TextContentSection from '@/sections/text-content-section';
import ImageGridSection from '@/sections/image-grid-section';
import CtaSection from '@/sections/cta-section';
type SectionProps = Record<string, unknown>;
// Section mapping configuration
export const sectionMap: Record<string, ComponentType<SectionProps>> = {
hero: HeroSection,
smallHero: SmallHeroSection,
text: TextContentSection,
imageGrid: ImageGridSection,
cta: CtaSection,
};
// Context configuration for navigations and collections
export const contextData = {
navigations: [
{ key: 'mainNavigation', id: 'nav-a01c4cbb-21f7-46d5-a89c-564307998128' },
],
collections: [
{ key: 'news', id: 'col-a01c8223-4e4a-40aa-90d9-70149e87322c' },
],
};
Naming Convention
- Brease section type:
hero(lowercase, camelCase) - Component name:
HeroSection(PascalCase) - Map key:
hero(matches Brease exactly)
Step 6: Create Navigation Component
Use the useBrease hook to access navigation data.
Create src/components/navigation.tsx
'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 className="bg-white shadow">
<div className="mx-auto max-w-7xl px-6">
<div className="flex h-16 items-center justify-between">
<div className="flex space-x-8">
{mainNavigation.items.map((item, index) => {
if (item.type === 'internal' && item.target) {
return (
<Link
key={index}
href={item.target.slug}
className="text-gray-900 hover:text-gray-600"
>
{item.value}
</Link>
);
}
if (item.url) {
return (
<a
key={index}
href={item.url}
target="_blank"
rel="noopener noreferrer"
className="text-gray-900 hover:text-gray-600"
>
{item.value}
</a>
);
}
return null;
})}
</div>
</div>
</div>
</nav>
);
}
Navigation Item Types
- Internal Link: Has
item.target.slug(links to Brease pages) - External Link: Has
item.url(links to external URLs)
Step 7: Implement Collection Routes
For blog posts, products, or other collection-based content.
Create src/app/news/[slug]/page.tsx
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { fetchEntryBySlug, generateBreaseCollectionParams, type BreaseMedia } from 'brease-next';
export const dynamic = 'auto';
const NEWS_COLLECTION_ID = 'col-a01c8223-4e4a-40aa-90d9-70149e87322c';
export async function generateStaticParams() {
return await generateBreaseCollectionParams(NEWS_COLLECTION_ID);
}
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }): Promise<Metadata> {
const { slug } = await params;
const result = await fetchEntryBySlug(NEWS_COLLECTION_ID, `/${slug}`);
if (!result.success) {
return {};
}
const entry = result.data;
return {
title: entry.elements.title as string,
description: entry.elements.excerpt as string,
openGraph: {
title: entry.elements.title as string,
description: entry.elements.excerpt as string,
type: 'article',
images: entry.elements.featuredImage
? [{ url: (entry.elements.featuredImage as BreaseMedia).path }]
: undefined,
},
};
}
export default async function NewsArticle({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const result = await fetchEntryBySlug(NEWS_COLLECTION_ID, `/${slug}`);
if (!result.success) {
if (result.status === 404) {
return notFound();
}
throw new Error(`Failed to load article: ${result.error}`);
}
const entry = result.data;
const title = entry.elements.title as string;
const content = entry.elements.content as string;
const featuredImage = entry.elements.featuredImage as BreaseMedia;
return (
<article className="mx-auto max-w-4xl px-6 py-12">
<h1 className="text-4xl font-bold mb-4">{title}</h1>
{featuredImage && (
<img
src={featuredImage.path}
alt={featuredImage.alt}
className="w-full rounded-lg mb-8"
/>
)}
<div
className="prose"
dangerouslySetInnerHTML={{ __html: content }}
/>
</article>
);
}
Finding Collection IDs
Collection IDs are UUIDs from Brease. To find them:
- Open Brease dashboard
- Navigate to Collections
- Copy the UUID from the URL or collection settings
- Add it to
contextDatainbrease-config.ts
Step 8: Use Collections from Context
Access collection data from the context in client components.
Example: Latest News Section
'use client';
import { useBrease } from 'brease-next';
import Link from 'next/link';
export default function LatestNewsSection() {
const { collections } = useBrease();
const { news } = collections;
if (!news || news.length === 0) {
return null;
}
return (
<div className="mx-auto max-w-7xl px-6 py-12">
<h2 className="text-3xl font-bold mb-8">Latest News</h2>
<div className="grid gap-8 md:grid-cols-3">
{news.slice(0, 3).map((article) => {
const title = article.elements.title as string;
const excerpt = article.elements.excerpt as string;
return (
<Link
key={article.slug}
href={`/news${article.slug}`}
className="block group"
>
<article className="border rounded-lg overflow-hidden hover:shadow-lg transition">
<div className="p-6">
<h3 className="text-xl font-semibold mb-2 group-hover:text-blue-600">
{title}
</h3>
<p className="text-gray-600">{excerpt}</p>
</div>
</article>
</Link>
);
})}
</div>
</div>
);
}
Step 9: Build and Test
Development
npm run dev
Visit http://localhost:3000 to test your implementation.
Build for Production
npm run build
This will:
- Fetch all pages and collections from Brease
- Generate static HTML for each page
- Fetch and apply redirects
- Optimize images
Test Static Build
npm run build
npm start
Common Patterns
Loading States
Handle loading states in client components:
'use client';
import { useBrease } from 'brease-next';
export default function Navigation() {
const { navigations } = useBrease();
const { mainNavigation } = navigations;
if (!mainNavigation) {
return <div>Loading...</div>;
}
return (
<nav>
{/* navigation items */}
</nav>
);
}
Error Boundaries
Create an error boundary for better error handling:
// src/app/error.tsx
'use client';
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<div>
<h2>Something went wrong!</h2>
<button onClick={() => reset()}>Try again</button>
</div>
);
}
Not Found Pages
Create a custom 404 page:
// src/app/not-found.tsx
export default function NotFound() {
return (
<div>
<h2>Not Found</h2>
<p>Could not find requested resource</p>
</div>
);
}
Deployment Checklist
- All environment variables set in production
- Build completes successfully
- All pages render correctly
- Navigation works
- Images load properly
- Metadata appears in page source
- Redirects work as expected
- 404 pages return correct status code
Next Steps
Review Examples for complete working code samples, or consult Troubleshooting if you encounter issues.