Implementation Patterns
Pages & Dynamic Routes
Brease sites use a single optional catch-all route ([[...slug]]) to handle every page, including the homepage. This page explains the route structure, layout, page component, static generation, and section components in detail.
Route Structure
app/
[[...slug]]/
layout.tsx ← BreaseContext wrapper (server component)
page.tsx ← Page rendering + metadata (server component)
The double-bracket [[...slug]] syntax makes the slug parameter optional. This means:
/matches withslugasundefined/aboutmatches withslugas['about']/sk/about-usmatches withslugas['sk', 'about-us']
No separate locale segment needed
You do not need a [locale] route segment. Locale prefixes (e.g. sk/about-us) are part of the slug and handled automatically by fetchPage and BreaseContext.
Layout (layout.tsx)
The layout is a server component that wraps every page with BreaseContext. It receives the slug from route params and passes it to the context provider.
// src/app/[[...slug]]/layout.tsx
import { BreaseContext } from 'brease-next'
import { contextData } from '@/lib/brease/config'
import { getPage } from '@/lib/brease/get-page'
export default async function CatchAllLayout({
children,
params,
}: {
children: React.ReactNode
params: Promise<{ slug?: string[] }>
}) {
const { slug } = await params
const slugStr = (slug ?? []).join('/')
return (
<BreaseContext config={contextData} slug={slugStr} getPage={getPage}>
{children}
</BreaseContext>
)
}
What BreaseContext does
When the layout renders, BreaseContext:
- Fetches the page data using
getPage(orfetchPageinternally ifgetPageis not provided) - Fetches all configured navigations and collections (with the correct locale derived from the slug)
- Fetches available locales and alternate links
- Makes all of this data available to child client components via the
useBrease()hook
The getPage function
Using React cache ensures the page is only fetched once per render pass, even if both the layout and page component call it:
// src/lib/brease/get-page.ts
import { cache } from 'react'
import { fetchPage } from 'brease-next/server'
export const getPage = cache(async (slug: string) => {
return fetchPage(slug)
})
Page (page.tsx)
The page component handles three concerns: static generation, metadata, and rendering.
// src/app/[[...slug]]/page.tsx
import { notFound } from 'next/navigation'
import {
BreasePage,
BreaseStructuredData,
BreaseCustomCode,
generateBreasePageParams,
generateBreasePageMetadata,
ensureSuccess,
} from 'brease-next'
import { componentMap } from '@/lib/brease/config'
import { getPage } from '@/lib/brease/get-page'
// 1. Static generation — tell Next.js which pages to pre-render
export async function generateStaticParams() {
return generateBreasePageParams()
// Returns: [
// { locale: 'en', slug: ['about'] },
// { locale: 'sk', slug: ['sk', 'o-nas'] },
// ...
// ]
}
// 2. Metadata — SEO tags, Open Graph, Twitter Cards
export async function generateMetadata({
params,
}: {
params: Promise<{ slug?: string[] }>
}) {
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',
})
}
// 3. Rendering — fetch page and render sections
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) {
notFound()
}
const page = ensureSuccess(result)
return (
<>
<BreaseStructuredData page={page} />
<BreasePage page={page} sectionMap={componentMap} />
<BreaseCustomCode page={page} />
</>
)
}
generateStaticParams
generateBreasePageParams() iterates all locales and all pages, returning an array of { locale: string; slug: string[] } objects. Next.js uses this to pre-render every page at build time.
generateMetadata
generateBreasePageMetadata takes a page object (not a slug string) and returns a Next.js Metadata object containing:
titleanddescriptionfrom the page's meta fieldsopenGraphdata (title, description, image, type)twitterCarddatarobotsindexing directives based onpage.indexingcanonicalUrlif setalternatesfor multi-locale sites
The optional metadataBase is prepended to relative URLs in Open Graph images and canonical URLs.
404 Handling
If getPage returns a failed response (page not found or API error), call notFound() from next/navigation. This triggers Next.js to render your not-found.tsx page with a 404 status code.
// src/app/not-found.tsx
export default function NotFound() {
return (
<div className="flex min-h-screen items-center justify-center">
<h1 className="text-4xl font-bold">404 — Page Not Found</h1>
</div>
)
}
Section Components
BreasePage iterates through page.sections and renders a component for each one by matching section.key against the keys in your sectionMap.
Matching uses section.key
Sections are matched by their key field, not by section.name or any other property. Make sure the keys in your componentMap exactly match the keys assigned to sections in the CMS.
Section component rules
- Section components must be client components — add
'use client'at the top of every section file.BreasePagerenders sections inside a client component tree, so they cannot be server components. - They receive the section's
elementsspread as props - Type the props with a custom interface that matches your CMS schema
Example: HeroSection
Suppose your CMS has a section with key hero and elements: title (text), subtitle (text), image (media).
// src/sections/hero-section.tsx
'use client'
import { BreaseImage, type BreaseMedia } from 'brease-next'
interface HeroSectionProps {
title: string
subtitle: string
image: BreaseMedia
}
export default function HeroSection({
title,
subtitle,
image,
}: HeroSectionProps) {
return (
<section className="relative py-24 px-6 text-center">
<h1 className="text-5xl font-bold">{title}</h1>
{subtitle && <p className="mt-4 text-xl text-gray-600">{subtitle}</p>}
{image && (
<div className="mt-12 mx-auto max-w-4xl">
<BreaseImage breaseImage={image} variant="xl" priority />
</div>
)}
</section>
)
}
Registering sections
// src/lib/brease/config.ts
import { ComponentType } from 'react'
import { SectionElementProps } from 'brease-next'
import HeroSection from '@/sections/hero-section'
import TextSection from '@/sections/text-section'
import CtaSection from '@/sections/cta-section'
export const componentMap: Record<string, ComponentType<SectionElementProps>> = {
hero: HeroSection as unknown as ComponentType<SectionElementProps>,
text: TextSection as unknown as ComponentType<SectionElementProps>,
cta: CtaSection as unknown as ComponentType<SectionElementProps>,
}
Handling rich text elements
Rich text fields from the CMS contain HTML. Render them with dangerouslySetInnerHTML:
'use client'
interface TextSectionProps {
heading: string
body: string // HTML from the CMS rich text editor
}
export default function TextSection({ heading, body }: TextSectionProps) {
return (
<section className="py-16 px-6 max-w-3xl mx-auto">
<h2 className="text-3xl font-bold mb-6">{heading}</h2>
<div
className="prose prose-lg"
dangerouslySetInnerHTML={{ __html: body }}
/>
</section>
)
}
Missing section keys
If a section's key has no matching entry in componentMap, a warning is logged to the console and the section is skipped. Other sections continue rendering normally.
Next Steps
- Collection Patterns — working with FAQs, team members, and other collection data
- Navigation Patterns — rendering header and footer menus
- Multi-Locale Sites — locale-aware routing and language switching