# Brease Documentation > The complete developer documentation for the Brease headless CMS and the brease-next Next.js integration package. Brease is a headless CMS for agencies and teams: content editors work in the CMS, and developers use brease-next to fetch and render that content. --- # Brease Documentation Source: https://docs.brease.io/ The complete developer documentation for the Brease headless CMS and the `brease-next` integration package. {% .lead %} {% quick-links %} {% quick-link title="Core Concepts" icon="presets" href="/docs/core-concepts" description="Understand the key entities — Sites, Environments, Sections, Collections, Templates, and how they relate to each other." /%} {% quick-link title="Content Modeling" icon="lightbulb" href="/docs/entity-hierarchy" description="Learn how to structure content in Brease: the entity hierarchy, element types, and recommended patterns." /%} {% quick-link title="Getting Started" icon="installation" href="/docs/getting-started" description="Install brease-next, configure your environment, and render your first CMS-driven page in minutes." /%} {% quick-link title="API Reference" icon="plugins" href="/docs/api-reference" description="Complete reference for all fetch functions, components, hooks, and types exported by brease-next." /%} {% /quick-links %} {% callout title="Using these docs with AI coding agents" %} These docs are available as plain Markdown for LLMs and AI coding tools (Claude Code, Cursor, Copilot, and others). Point your tool at [`/llms.txt`](/llms.txt) for an index linking to the raw Markdown of every page, or [`/llms-full.txt`](/llms-full.txt) for the entire documentation in a single file. Any page is also available as raw Markdown by appending `.md` to its URL — for example [`/docs/getting-started.md`](/docs/getting-started.md). {% /callout %} --- ## What is Brease? Brease is a headless content management system built for agencies and development teams who need a structured, collaborative way to manage website content. It consists of two parts: - **Brease CMS** — the admin panel where content editors and developers define content structures, manage pages, and collaborate in real-time. - **brease-next** — an npm package that provides a type-safe API client, React components, and utilities for rendering Brease content in Next.js applications. Content editors work in the CMS to create and manage pages, sections, collections, navigations, and media. Developers use `brease-next` to fetch that content and render it in their Next.js frontend. ## How content flows ``` Brease CMS (admin panel) ↓ defines structures, fills content Brease API (REST) ↓ delivers content as JSON brease-next (npm package) ↓ fetches, provides context, renders Your Next.js website ``` ## Where to start **If you are new to Brease**, start with [Core Concepts](/docs/core-concepts) to understand the data model, then follow the [Getting Started](/docs/getting-started) guide to set up your first project. **If you need to model content**, read the [Content Modeling](/docs/entity-hierarchy) section to learn how to structure Sites, Sections, Collections, Templates, and Page Types. **If you are building a frontend**, jump to the [brease-next Package](/docs/getting-started) section for API reference, components, and implementation patterns. **If you need code examples**, see the [Implementation Patterns](/docs/project-setup) section for task-oriented guides covering routing, collections, navigation, locales, media, and deployment. --- # Core Concepts Source: https://docs.brease.io/docs/core-concepts Brease CMS is organized around a set of core entities. Understanding how they relate is essential before building with `brease-next`. --- ## Team An organizational unit that owns sites. Each team has members with roles (owner, admin, editor). When you sign up, a team is created automatically. ## Site A web project belonging to a team. Each site has a name, a production domain, and a preview domain. A site contains one or more environments. ## Environment A deployment context within a site (e.g., `main`, `staging`). Environments scope **all** content: pages, sections, collections, navigations, redirects, media, and locales. Each environment has a UUID used for API access. {% callout title="Environments are fully independent" type="note" %} Content in the `main` environment is completely separate from `staging`. You can build and preview changes in staging without affecting production. {% /callout %} ## Locale A language/region code (e.g., `en`, `sk`, `de-AT`). Each environment can have multiple locales, with one marked as the default. All content — page sections, collection entries, navigation items — is stored per locale. ## Page A URL route within an environment. Each page has: - **slug**: the URL path (e.g., `/about`, `/services/consulting`) - **SEO metadata**: metaTitle, metaDescription, openGraph, twitterCard, canonicalUrl, structuredData - **sections**: ordered content blocks (see below) - **indexing**: toggle to control search engine indexing - **customCode**: HTML/JS injection into the page head or body ## Section A reusable content structure (schema) defined in the Editor. A section has a matrix of rows and slots — each slot holds an Element with a type and key. The section's **`key`** is what maps to a React component in `brease-next`'s `sectionMap`. For example, a section with key `hero` maps to `sectionMap.hero`. Sections can be **Global** — shared across multiple pages with the same content. ## Element A typed content field within a section or collection. Each element has a unique key within its parent and one of 14+ types: String, Long Text, Rich Text, Integer, Decimal, Boolean, DateTime, Media, Link, Collection, Reference, Option, List, JSON, Location. See [Element Types Reference](/docs/element-types) for the full list. ## Collection Like a Section, but designed for repeatable data. You define the structure once (with elements), then create many Entries. Think: partner logos, FAQs, team members. ## Entry A single record in a Collection. Each entry has: - **name**: identification - **status**: published or unpublished - **visibility**: hidden or shown - **ordering**: drag-and-drop reordering - **per-locale element content**: the actual data, scoped by locale ## Template An ordered bundle of Sections. A template defines "this page has Hero, then Content, then CTA." Used to standardize page layouts across your site. ## Page Type Combines a Template with Reference fields (page-level data). When you assign a Page Type to a new page, its template sections are pre-populated and reference fields become available as page-level data. ## Navigation A named container of navigation items, scoped per environment and localized per locale. Items can be: - **page**: internal link to a page - **url**: external URL - **media**: link to a media file - **group**: a dropdown container with nested children Items support arbitrary nesting for multi-level menus. ## Redirect A source/destination URL pair with a type: - **301**: permanent redirect - **302**: temporary redirect - **307**: temporary, preserves HTTP method - **308**: permanent, preserves HTTP method ## Media Uploaded files with automatic responsive variants: `sm`, `md`, `lg`, `xl`, `2xl`, `hd`, and `original`. Supports images, video, audio, and documents. --- ## Editor vs Builder vs Manager This is the most important distinction in Brease. | Tool | Purpose | What you do | | ----------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | **Editor** | Define section and collection **schemas** (the structure) | Create sections with rows, slots, and elements. Define collections with element fields. This is where you design _what_ content looks like. | | **Builder** | Fill in page section **content** (the data per page) | Select a page, then populate its section elements with actual content — text, images, links, etc. | | **Manager** | Fill in collection entry **content** (the data per entry) | Browse a collection, create entries, and fill in their element fields with data. | {% callout title="Think of it this way" type="note" %} The **Editor** is the architect (designs the blueprint). The **Builder** is the interior designer (decorates each room/page). The **Manager** is the librarian (manages the catalog/collection of entries). {% /callout %} --- # Entity Hierarchy Source: https://docs.brease.io/docs/entity-hierarchy Every piece of content in Brease follows a strict ownership and scoping chain. Understanding this hierarchy is key to working with the CMS and the `brease-next` package. --- ## The Hierarchy ``` Team └── Site └── Environment (e.g., main, staging) ├── Pages │ └── Page Sections (ordered content blocks) ├── Sections (schema definitions) ├── Collections │ └── Entries (individual records) ├── Templates (ordered bundles of sections) ├── Page Types (template + reference fields) ├── Navigations │ └── Navigation Items (nested tree) ├── Redirects ├── Media (files + variants) └── Locales (language/region codes) ``` ## Ownership Chain **Teams own Sites.** A team is the top-level organizational unit. All billing, membership, and permissions are managed at the team level. A team can own multiple sites. **Sites have Environments.** Each site has at least one environment. Environments let you maintain separate content contexts — for example, a `main` environment for production and a `staging` environment for preview. **Environments scope content.** Everything below the environment level — pages, sections, collections, navigations, redirects, media, and locales — is environment-scoped. The same section can exist in both `main` and `staging` with completely different content. ## Environment Scoping This is the most important architectural concept: **all content is scoped to an environment**. - A page in `main` and a page in `staging` with the same slug are independent entities - Sections defined in `main` do not exist in `staging` unless explicitly created there - Collection entries, navigations, and redirects are all per-environment - Media files are shared at the site level but their usage in content is per-environment ## Default Environment and Default Page Each site has a **default environment** — typically `main`. This is the environment used for production deployments. Each environment has a **default page** — the page served at the root URL (`/`). This is usually your homepage. ## API Access The `brease-next` package identifies which environment to fetch from using two values: | Variable | Purpose | |----------|---------| | `BREASE_ENV` | The environment UUID — scopes all API requests to this environment | | `BREASE_TOKEN` | The API token — authenticates requests to the public API | These are set as environment variables in your Next.js project: ```env BREASE_ENV=your_environment_uuid BREASE_TOKEN=your_api_token ``` Every fetch function in `brease-next` — `fetchPage()`, `fetchCollectionById()`, `fetchNavigation()`, etc. — automatically uses these values. You never need to pass the environment or token manually. {% callout title="On the frontend" type="note" %} In `brease-next`, you configure `BREASE_ENV` with your environment UUID and `BREASE_TOKEN` with your API token. All fetch functions are automatically scoped to that environment. To switch between environments (e.g., staging vs production), change the environment variables in your `.env.local` or deployment configuration. {% /callout %} --- # Sections & Elements Source: https://docs.brease.io/docs/sections-and-elements Sections and elements are the building blocks of all page content in Brease. A section defines the structure; elements define the fields within that structure. --- ## What is a Section? A section is a **reusable content structure** — like a database table definition. It specifies what fields (elements) exist and their types, but contains no data itself. Data is filled in per page via the Builder. For example, a "Hero" section might define: a `title` (string), a `subtitle` (long text), and an `image` (media). Every page that uses this section gets its own copy of those fields to fill in. ## Section Layout Sections use a **matrix layout** of rows and slots: - **Rows** are horizontal containers stacked vertically - Each row can have **multiple slots** side by side - Each slot holds **one Element** This matrix is a visual organizing tool in the CMS Editor — it does not affect frontend rendering. On the frontend, you receive a flat `elements` object keyed by element keys. ## Section Properties | Property | Description | | --------------- | ---------------------------------------------- | | **name** | Display name in the CMS (e.g., "Hero Section") | | **description** | Optional description for content editors | | **key** | Unique identifier used for frontend mapping | | **published** | Whether the section is available for use | ### The `key` Property The section `key` is critical — it maps a section to a React component in `brease-next`'s `sectionMap`. ```typescript // CMS: Section with key "hero" // Frontend: maps to HeroSection component export const sectionMap = { hero: HeroSection, // key "hero" → HeroSection features: FeaturesSection, // key "features" → FeaturesSection cta: CtaSection, // key "cta" → CtaSection } ``` If a section's key doesn't exist in the `sectionMap`, it is skipped during rendering. {% callout title="Section components must be client components" type="warning" %} Every component in the `sectionMap` must include `'use client'` at the top of the file. `BreasePage` is a client component and renders sections within a client component tree. {% /callout %} ## Elements Elements are **typed content fields** within a section. Each element has: | Property | Description | | ----------------- | ------------------------------------------------------------------------ | | **key** | Unique identifier within the section (used as prop name on the frontend) | | **type** | One of 14+ types (string, media, link, etc.) | | **required** | Whether the field must be filled in | | **configuration** | Type-specific options (e.g., media mimeGroup, option values) | Element keys become property names when accessing content on the frontend. A section with elements `title`, `subtitle`, and `heroImage` produces: ```typescript { title: "Welcome to our site", subtitle: "We build great things", heroImage: { path: "https://...", alt: "Hero", width: 1920, height: 1080, ... } } ``` See [Element Types Reference](/docs/element-types) for the full list of types and their frontend values. ## Global Sections Sections can be created as **global** in the Editor. A global section's content is shared across every page that uses it — editing it on one page updates it everywhere. Use cases: site-wide banners, shared CTAs, footer content blocks. ## How It Maps to the Frontend ``` CMS Editor: Section "hero" with elements: title (string), subtitle (long-text), image (media) ↓ Frontend sectionMap: { hero: HeroSection } ↓ HeroSection component receives: { title: "Welcome", subtitle: "...", image: { path: "...", variants: [...] } } ``` A complete example: ```typescript // 1. Define the component (must be a client component) '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 (

{title}

{subtitle}

); } // 2. Register in sectionMap export const sectionMap = { hero: HeroSection, }; // 3. BreasePage renders it automatically ``` {% callout title="On the frontend" type="note" %} Section content arrives in `brease-next` as `section.elements` — a `Record` keyed by element keys. Use `sectionMap` to map section keys to React components. Each component receives the elements object spread as props. {% /callout %} --- # Collections & Entries Source: https://docs.brease.io/docs/collections-and-entries Collections let you manage repeatable, structured data — FAQs, team members, products, and more. --- ## What is a Collection? A collection is like a Section, but designed for **repeatable data**. You define the structure once (with elements using the same drag-and-drop Editor interface), then create many Entries that follow that structure. | Concept | Section | Collection | | ------------------------ | ---------------------- | ------------------- | | **Structure defined in** | Editor | Editor | | **Content filled in** | Builder (per page) | Manager (per entry) | | **Content instances** | One per page placement | Many entries | | **Use case** | Page layouts | Lists of data | **Common use cases:** FAQs, team members, products, testimonials, portfolio items, job listings, event listings, partner logos. ## Defining a Collection Collections are created in the **Editor** using the same interface as sections — rows, slots, and elements. {% callout title="No circular references" type="warning" %} Collections cannot use the `collection` element type. This prevents circular references where a collection would contain another collection. {% /callout %} ## Entries An entry is a single record in a collection. Each entry has: | Property | Description | | -------------- | ---------------------------------------- | | **uuid** | Unique identifier | | **name** | Display name in the CMS | | **status** | Published or unpublished | | **visibility** | Hidden or shown | | **ordering** | Position in the list (drag-and-drop) | | **elements** | Per-locale content keyed by element keys | Only published entries with "shown" visibility are returned by the public API. --- ## Fetching via BreaseContext For collections needed globally (e.g., in navigation or footer), configure them in `BreaseContext`: ```typescript // src/lib/brease-config.ts export const contextData = { navigations: [], collections: [ { key: 'faqs', id: 'col-a01c8223-4e4a-40aa-90d9-70149e87322c' }, { key: 'testimonials', id: 'col-b02d9334-5f5b-51bb-a1ea-81250f98433d' }, ], } ``` Then access in client components: ```typescript 'use client'; import { useBrease } from 'brease-next'; export default function Testimonials() { const { collections } = useBrease(); const { testimonials } = collections; if (!testimonials || testimonials.length === 0) return null; return (
{testimonials.map(entry => (

{entry.elements.quote as string}

{entry.elements.author as string}
))}
); } ``` {% callout title="Type casting" type="note" %} Collection entry elements are untyped (`Record`). Always cast them to the appropriate type based on your collection schema: `entry.elements.title as string`, `entry.elements.image as BreaseMedia`, etc. {% /callout %} --- # Templates & Page Types Source: https://docs.brease.io/docs/templates-and-page-types Templates and Page Types help you standardize page layouts and attach page-level metadata across your site. --- ## Templates A template is an **ordered bundle of sections**. It defines "this page layout has these sections in this order." Instead of manually adding sections to every new page, you create a template once and reuse it. ### Creating a Template 1. Go to **Editor → Templates** 2. Select from existing **published sections** 3. Reorder sections as needed 4. Publish the template A template named "Blog Layout" might contain: ``` 1. Hero Section (key: smallHero) 2. Content Section (key: text) 3. Related Posts Section (key: latestNews) ``` ### Purpose Templates solve a common problem: ensuring consistency. When multiple pages share the same structure (e.g., all blog posts have a hero, content area, and related posts), a template guarantees they all use the same sections in the same order. --- ## Page Types A Page Type combines a **Template** with **Reference fields**. Reference fields use the same element types (string, media, link, datetime, etc.) and act as **page-level metadata** — data that belongs to the page itself rather than to any specific section. ### Use Case A "Blog Post" Page Type could have: - **Template**: "Blog Layout" (smallHero + text + latestNews sections) - **Reference fields**: - `author` (string) — who wrote it - `publishDate` (datetime) — when it was published - `featuredImage` (media) — for social sharing and listings - `category` (option) — blog category This is useful for example when you want to create components that uses content from a page without fetching the full content of that page (e.g: Latest News cards from blog post pages) When you create a page with this Page Type, the template sections are automatically added and the reference fields appear as page-level metadata fields. ### Reference Fields vs Section Elements | | Reference Fields | Section Elements | | -------------- | -------------------------------------- | ------------------------------------- | | **Scope** | Page-level | Section-level | | **Defined in** | Page Type | Section schema | | **Filled in** | Page settings | Builder | | **Use case** | Page metadata (author, date, category) | Section content (title, body, images) | --- ## Recommended Workflow {% callout title="Build bottom-up" type="note" %} Follow this order to build your content architecture efficiently. Each step builds on the previous one. {% /callout %} ### 1. Define Sections in the Editor Create the individual content blocks your site needs: hero, text, CTA, image grid, testimonials, etc. Each section defines its own elements (fields). ### 2. Compose Sections into Templates Group sections into reusable page layouts. A "Landing Page" template might combine hero + features + testimonials + CTA. A "Blog Post" template might combine smallHero + text + relatedPosts. ### 3. Wrap Templates in Page Types with Reference Fields Add page-level metadata fields to your templates. A "Blog Post" Page Type adds author, publish date, and category on top of the blog layout template. ### 4. Create Pages Using Page Types When creating a new page, select a Page Type. The template sections are pre-populated and reference fields are ready to fill in. Content editors can focus on writing instead of assembling page structure. --- ## On the Frontend Reference field data arrives in `page.references` as an array of objects. Access it via the `useBrease()` hook in client components: ```typescript 'use client'; import { useBrease } from 'brease-next'; export default function BlogMeta() { const { references } = useBrease(); if (!references || references.length === 0) return null; return (
{references.map((ref, index) => ( {ref.key}: {String(ref.value)} ))}
); } ``` {% callout title="References are page-scoped" type="note" %} Reference data is specific to each page. It is available via `useBrease().references` in client components and directly on the page object in server components. {% /callout %} --- # Navigations & Redirects Source: https://docs.brease.io/docs/navigations-and-redirects Navigations and redirects are managed per environment in the CMS and consumed on the frontend via `brease-next`. --- ## Navigations A navigation is a **named container of navigation items**, scoped per environment and localized per locale. ### Item Types In the CMS, each navigation item is one of: **page** (internal link), **url** (external link), **media** (file link), **entry** (collection entry link), or **group** (dropdown container with children). On the frontend, all items arrive as `BreaseNavigationItem`: ```typescript interface BreaseNavigationItem { uuid: string label: string // Display text isExternal: boolean // false for pages, true for external URLs value: string // The URL or slug target: '_blank' | '_self' | null children: BreaseNavigationItem[] // Nested items } ``` Use the `isExternal` flag and `value` to determine link behavior — or simply pass the item to `BreaseLink` which handles this automatically. ### Ordering and Nesting Items are reordered via drag-and-drop in the CMS. Nesting is supported — drag items under a group or another item to create multi-level dropdown menus. Nested items appear in the `children` array. ### On the Frontend Configure navigations in `BreaseContext`: ```typescript // src/lib/brease-config.ts export const contextData = { navigations: [ { key: 'header', id: 'nav-a01c4cbb-21f7-46d5-a89c-564307998128' }, { key: 'footer', id: 'nav-b02d5ecc-32f8-57e6-b9ad-675418009239' }, ], collections: [], } ``` Access in client components via `useBrease()` and render items with `BreaseLink`: ```typescript 'use client' import { useBrease, BreaseLink } from 'brease-next' export default function Header() { const { navigations } = useBrease() const headerNav = navigations.header if (!headerNav) return null return ( ) } ``` `BreaseLink` handles internal vs external links automatically — it renders a Next.js `Link` for internal URLs and a native `` with `rel="noopener noreferrer"` for external ones. Since `BreaseNavigationItem` extends `BreaseLinkData`, you can pass navigation items directly as `linkData`. For nested items (dropdowns), iterate `item.children`: ```typescript {item.children.map((child) => ( {child.label} ))} ``` {% callout title="Finding navigation UUIDs" type="note" %} Open the Brease dashboard, go to **Navigations**, select your navigation, and copy the UUID from the settings panel or URL. {% /callout %} --- ## Redirects Redirects are **source/destination URL pairs** with an HTTP status type, managed per environment in the CMS. ### Redirect Types | Type | Name | Behavior | | ------- | ------------------ | --------------------------------------------------------- | | **301** | Moved Permanently | Browsers and search engines cache this permanently | | **302** | Found (Temporary) | Temporary redirect, not cached | | **307** | Temporary Redirect | Like 302, but preserves the HTTP method (POST stays POST) | | **308** | Permanent Redirect | Like 301, but preserves the HTTP method | ### On the Frontend Fetch redirects and apply them in `next.config.ts`: ```typescript // next.config.ts import type { NextConfig } from 'next' import { fetchRedirects } from 'brease-next/server' const nextConfig: NextConfig = { 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', })) }, } export default nextConfig ``` {% callout title="Build-time only" type="warning" %} Next.js redirects are evaluated at **build time**. After adding or changing redirects in the CMS, you need to rebuild and redeploy your site for the changes to take effect. {% /callout %} ### Redirect Format When creating redirects in the CMS: - **Source**: the path to redirect from (e.g., `/old-page`) - **Destination**: the path to redirect to (e.g., `/new-page`) - Both must include leading slashes - Supports Next.js path matching patterns (e.g., `/blog/:slug`) --- # Multi-Locale Content Source: https://docs.brease.io/docs/multi-locale Brease supports multi-locale content out of the box. Each environment can have multiple locales, and all content is stored independently per locale. --- ## Locales in the CMS Each environment can have multiple locales (e.g., `en`, `sk`, `de-AT`). One locale is marked as the **default**. All content entities have per-locale content: - **Pages**: section content is localized per locale - **Collection entries**: element content is localized per locale - **Navigation items**: labels and values are localized per locale In the CMS, the **Builder** and **Manager** have a locale switcher to edit content for each locale independently. You fill in content for one locale at a time. ## Configuring Locales in `brease-next` Set the default locale in your environment variables: ```env BREASE_DEFAULT_LOCALE=en ``` This tells `brease-next` which locale to use when no locale is detected from the URL. ## Locale Derivation from URLs `brease-next` determines the active locale from the **first segment** of the URL slug: | URL | Detected Locale | Slug Passed to API | |-----|----------------|-------------------| | `/about` | default (`en`) | `/about` | | `/sk/about` | `sk` | `/sk/about` | | `/de-AT/kontakt` | `de-AT` | `/de-AT/kontakt` | The first segment is checked against known locale codes for the environment. If it matches a configured locale, that locale is used. Otherwise, the default locale is used. ## Fetching Available Locales Use `fetchLocales()` to get all active locales for the current environment: ```typescript import { fetchLocales } from 'brease-next'; const result = await fetchLocales(); if (result.success) { console.log('Available locales:', result.data); // [{ code: 'en', name: 'English', default: true }, { code: 'sk', name: 'Slovak', default: false }] } ``` ## Alternate Links (hreflang) Each page includes `alternateLinks` — a `Record` mapping locale codes to URLs. Use these for SEO `hreflang` tags: ```typescript import { fetchPage } from 'brease-next'; const result = await fetchPage('/about'); if (result.success) { const alternateLinks = result.data.alternateLinks; // { en: "/about", sk: "/sk/o-nas", de: "/de/ueber-uns" } } ``` In client components, access via the `useBrease()` hook: ```typescript 'use client'; import { useBrease } from 'brease-next'; export default function HreflangTags() { const { alternateLinks } = useBrease(); if (!alternateLinks) return null; return ( <> {Object.entries(alternateLinks).map(([locale, url]) => ( ))} ); } ``` ## Language Switcher Build a language selector using `useBrease()`: ```typescript 'use client'; import { useBrease } from 'brease-next'; import Link from 'next/link'; export default function LanguageSwitcher() { const { alternateLinks, availableLocales } = useBrease(); if (!alternateLinks || !availableLocales) return null; return (
{availableLocales.map(locale => ( {locale.name} ))}
); } ``` ## Static Generation with Multiple Locales `generateBreasePageParams()` iterates all locales and all pages to generate params for `[[...slug]]` catch-all routes: ```typescript // src/app/[[...slug]]/page.tsx import { generateBreasePageParams, fetchPage, BreasePage } from 'brease-next'; import { sectionMap } from '@/lib/brease-config'; import { notFound } from 'next/navigation'; export async function generateStaticParams() { return generateBreasePageParams(); // Returns params for all locale/page combinations: // [ // { slug: [] }, // homepage (default locale) // { slug: ['about'] }, // /about (default locale) // { slug: ['sk'] }, // /sk (Slovak homepage) // { slug: ['sk', 'o-nas'] }, // /sk/o-nas (Slovak about page) // ] } export default async function Page({ params }: { params: Promise<{ slug?: string[] }> }) { const { slug } = await params; const pageSlug = slug ? `/${slug.join('/')}` : '/'; const result = await fetchPage(pageSlug); if (!result.success) { if (result.status === 404) return notFound(); throw new Error(`Failed to load page: ${result.error}`); } return ; } ``` {% callout title="Locale-aware content" type="note" %} All `brease-next` fetch functions automatically resolve the correct locale based on the slug you pass. You don't need to pass a locale parameter separately — the locale is derived from the URL structure. {% /callout %} --- # Element Types Reference Source: https://docs.brease.io/docs/element-types Brease supports 14+ element types for defining content fields in sections and collections. This reference covers each type, its frontend value, configuration options, and rendering guidance. --- ## Type Reference ### String | | | |-|-| | **Key** | `string` | | **Frontend value** | `string` | | **Config** | `required` | Plain text, single line. Render directly. ```typescript

{elements.title as string}

``` ### Long Text | | | |-|-| | **Key** | `long-text` | | **Frontend value** | `string` | | **Config** | `required` | Multi-line plain text. May contain newlines. Use `white-space: pre-line` in CSS or split on `\n`: ```typescript

{elements.description as string}

``` ### Rich Text | | | |-|-| | **Key** | `richtext` | | **Frontend value** | `string` (HTML) | | **Config** | `required` | HTML string from CKEditor. Render with `dangerouslySetInnerHTML`: ```typescript
``` {% callout title="Sanitization" type="warning" %} Rich text content is HTML generated by CKEditor in the CMS. If you need additional sanitization, use a library like `DOMPurify` before rendering. {% /callout %} ### Integer | | | |-|-| | **Key** | `integer` | | **Frontend value** | `number` | | **Config** | `required` | Whole number. Render directly. ```typescript {elements.count as number} items ``` ### Decimal | | | |-|-| | **Key** | `float` | | **Frontend value** | `number` | | **Config** | `required` | Floating-point number. ```typescript ${(elements.price as number).toFixed(2)} ``` ### Boolean | | | |-|-| | **Key** | `boolean` | | **Frontend value** | `boolean` | | **Config** | `required` | Toggle/switch value. Use for conditional rendering: ```typescript {(elements.showBanner as boolean) && } ``` ### DateTime | | | |-|-| | **Key** | `datetime` | | **Frontend value** | `string` (ISO 8601) | | **Config** | `required` | ISO 8601 date string. Parse with `new Date()` or a date library: ```typescript const date = new Date(elements.publishDate as string); ``` ### Media | | | |-|-| | **Key** | `media` | | **Frontend value** | `BreaseMedia` or `BreaseMedia[]` | | **Config** | `required`, `multiple` (boolean), `mimeGroup` (image/video/audio/document/archive) | Uploaded file with metadata and responsive variants. Check the `multiple` config to know if the value is an array. ```typescript import { BreaseImage, type BreaseMedia } from 'brease-next'; // Single media // Multiple media {(elements.gallery as BreaseMedia[]).map((img, i) => ( ))} ``` The `BreaseMedia` object includes: ```typescript { path: string; // Full URL to original file alt: string; // Alt text name: string; // File name width: number; // Original width height: number; // Original height mimeType: string; // e.g., "image/jpeg" mimeGroup: string; // "image", "video", "audio", "document" variants: BreaseMediaVariant[]; // Responsive variants (sm, md, lg, xl, 2xl, hd) } ``` ### Link | | | |-|-| | **Key** | `link` | | **Frontend value** | `BreaseLinkData` — `{ label: string, isExternal: boolean, value: string, target: '_blank' \| '_self' \| null }` | | **Config** | `required`, `allowedTypes` (page/url/media/entry) | Use the `BreaseLink` component — it handles internal vs external links automatically: ```tsx import { BreaseLink, type BreaseLinkData } from 'brease-next' const link = elements.cta as BreaseLinkData {link.label} ``` `BreaseLink` renders a Next.js `Link` for internal URLs and a native `` with `rel="noopener noreferrer"` for external ones. It respects the `target` property from the CMS. ### Collection | | | |-|-| | **Key** | `collection` | | **Frontend value** | `{ name: string, entries: BreaseCollectionEntry[] }` | | **Config** | `collectionId` (linked collection UUID) | {% callout title="Sections only" type="warning" %} The collection element type is only available in Sections, not in Collections (to prevent circular references). {% /callout %} Contains the linked collection's entries inline within the section: ```typescript import type { BreaseCollection } from 'brease-next' const testimonials = elements.testimonials as BreaseCollection {testimonials.entries.map(entry => (

{entry.elements.quote as string}

{entry.elements.author as string}
))} ``` ### Reference | | | |-|-| | **Key** | `reference` | | **Frontend value** | `object` | | **Config** | - | Used in Page Types for page-level metadata. The value shape depends on the element configuration within the Page Type definition. ### Option | | | |-|-| | **Key** | `option` | | **Frontend value** | `string` or `string[]` | | **Config** | `values` (array of options), `type` (`select`/`radio`/`checkbox`) | Single value for `select`/`radio`, array for `checkbox`: ```typescript // Select or radio — single value const color = elements.theme as string; // "dark" // Checkbox — array of values const tags = elements.categories as string[]; // ["news", "featured"] {tags.map(tag => ( {tag} ))} ``` ### List | | | |-|-| | **Key** | `list` | | **Frontend value** | `unknown[]` | | **Config** | `required` | Repeater field. An array of items: ```typescript const items = elements.features as string[];
    {items.map((item, i) => (
  • {item}
  • ))}
``` ### JSON | | | |-|-| | **Key** | `json` | | **Frontend value** | `any` | | **Config** | `required` | Raw JSON data. Parse and use as needed: ```typescript const config = elements.settings as { color: string; size: number };
Custom styled content
``` ### Location | | | |-|-| | **Key** | `location` | | **Frontend value** | `{ lat: number, lng: number }` | | **Config** | `required` | Geographic coordinates. Use with map libraries: ```typescript const location = elements.officeLocation as { lat: number; lng: number }; // Use with Google Maps, Mapbox, Leaflet, etc. ``` --- ## Quick Reference Table | Type | Key | Frontend Value | Config Options | |------|-----|----------------|----------------| | String | `string` | `string` | `required` | | Long Text | `long-text` | `string` | `required` | | Rich Text | `richtext` | `string` (HTML) | `required` | | Integer | `integer` | `number` | `required` | | Decimal | `float` | `number` | `required` | | Boolean | `boolean` | `boolean` | `required` | | DateTime | `datetime` | `string` (ISO 8601) | `required` | | Media | `media` | `BreaseMedia` / `BreaseMedia[]` | `required`, `multiple`, `mimeGroup` | | Link | `link` | `BreaseLinkData` | `required`, `allowedTypes` | | Collection | `collection` | `BreaseCollection` | `collectionId` | | Reference | `reference` | `object` | - | | Option | `option` | `string` / `string[]` | `values`, `type` | | List | `list` | `unknown[]` | `required` | | JSON | `json` | `any` | `required` | | Location | `location` | `{ lat, lng }` | `required` | {% callout title="Type safety" type="note" %} All element values arrive as `unknown` from the API. Always cast them to the expected type based on your section/collection schema. Define TypeScript interfaces for your section props to get compile-time safety. {% /callout %} --- # Editor (Definitions) Source: https://docs.brease.io/docs/editor The Editor is where developers define content structures — the schemas that determine what content editors can fill in. Nothing is "content" here; everything is structure. --- ## Four Tabs The Editor is organized into four tabs, each managing a different entity type: ### Sections Define section schemas with drag-and-drop element layout. A section is a reusable content block structure (e.g., "Hero", "Feature Grid", "CTA Banner"). Sections are used by the Builder when editors populate page content. ### Collections Same editing experience as sections, but designed for repeatable data — FAQs, team members, testimonials, products. The key difference: the `collection` element type is filtered out of the element palette to prevent circular references (a collection cannot reference itself or another collection within its own definition). ### Templates Compose sections into ordered bundles that standardize page layouts. A template defines "this page should have Hero, then Features, then CTA" — giving content editors a pre-built structure when they create a new page. ### Page Types Combine a template with reference fields for page-level metadata. When a page is assigned a page type, it inherits the template's sections and gains the reference fields as page-level configuration options. --- ## Section and Collection Editing Both sections and collections share the same editing interface. ### Sidebar The left sidebar lists all sections or collections for the current environment. The sidebar provides: - **Search**: filter items by name - **Create New**: add a new section or collection ### Item Header Each item displays: - **Name**: the display name shown in the CMS - **Description**: optional description for content editors - **Status badge**: `Published` or `Unpublished` - **Page usage count**: how many pages currently use this section ### Canvas The main editing area is a drag-and-drop canvas with a matrix layout: - **Rows**: horizontal containers stacked vertically. Add rows to organize elements into logical groups. - **Slots**: each row contains one or more slots arranged side by side. Each slot holds exactly one element. The canvas layout is a visual organizing tool for the CMS — it does not affect how elements are delivered to the frontend. On the frontend, you always receive a flat `elements` object keyed by element key. ### Kiosk (Element Palette) The bottom bar is the **Kiosk** — the source palette for all available element types. To add an element to the canvas, drag it from the kiosk and drop it into an empty slot. Available element types include: String, Long Text, Rich Text, Integer, Decimal, Boolean, DateTime, Media, Link, Collection, Reference, Option, List, JSON, Location, and more. See [Element Types Reference](/docs/element-types) for the full list. ### Element Slot Configuration Click an element slot to configure it: - **Key**: the unique identifier for this element within the section. Must be unique across all elements in the section. - **Localized**: toggle whether this element has separate content per locale. When **on**, content editors see different values for each locale in the Builder/Manager. When **off**, the element shares a single value across all locales — useful for fields like images, coordinates, or IDs where the content is language-independent. - **Media settings**: for media elements — allow multiple files, restrict by MIME group (images, video, audio, documents) - **Collection link**: for collection elements — select which collection this element references - **Option values**: for option elements — define the selectable values and option type (select/radio/checkbox) - **Link restrictions**: for link elements — restrict which link types are available (page, URL, media, entry) {% callout title="Keys become frontend property names" type="warning" %} Section and collection `key` values are critical — they become the property names used in frontend code via `sectionMap` and `elements` object. Changing a key after content has been entered will break the link between existing content and your frontend components. {% /callout %} ### Validation Every element slot has a **Validation** panel (expandable in the slot configuration dropdown) with two settings: **Required** — a toggle available on all element types. When enabled, content editors must fill in this field before publishing. Boolean elements default to `required: false`; reference elements default to `required: true`. **Limit** — a numeric constraint available on most element types (not available on Boolean or Link elements). Controls how many values or how much content is acceptable. Three modes: | Mode | Fields | Use case | | ------------ | --------------- | ------------------------------------------------- | | **Between** | `min` and `max` | Enforce a range — e.g., "between 1 and 5 tags" | | **Min only** | `min` | Enforce a minimum — e.g., "at least 2 images" | | **Max only** | `max` | Enforce a maximum — e.g., "no more than 10 items" | How the limit applies depends on the element type: - **String / Long Text**: character count - **Rich Text**: character count - **Integer / Decimal**: value range - **Media** (multiple): number of files - **Option** (checkbox): number of selected values - **List**: number of items - **Collection**: number of entries {% callout title="Validation is enforced in the CMS" type="note" %} These validations are enforced when content editors save or publish in the Builder and Manager. They do not affect the frontend — `brease-next` delivers whatever data the API returns. If you need client-side validation, implement it separately in your components. {% /callout %} --- ## Global Sections A **Global Section** is a section whose content is shared across every page that uses it. Unlike regular sections — where each page gets its own independent copy of the content — a global section has a single set of content values that appear identically everywhere. ### Creating Global Sections Global sections are created in the **Editor**, just like regular sections. When creating or editing a section, mark it as global. Once marked, the section appears in the Builder's section picker alongside regular sections and can be added to any page. ### How Content Works - When a content editor adds a global section to a page in the Builder, they can edit its content there - Any changes to a global section's content apply to **every page** that includes it - Regular sections have independent content per page; global sections share a single set of content values ### When to Use Global Sections Global sections are ideal for content blocks that must stay consistent site-wide: - **CTA banners** — a single call-to-action shown on multiple pages - **Announcement bars** — a site-wide notice or promotion - **Partner logos** — a logo strip that appears across pages - **Shared testimonials** — a testimonial carousel reused on different pages {% callout title="Schema vs content" type="note" %} The global section's schema (structure and elements) is managed in the Editor. Its content values are managed in the Builder — but unlike regular sections, edits propagate to all pages that use it. {% /callout %} --- ## Template Editing Templates are ordered lists of sections: - **Add sections**: select from available published sections to add to the template - **Remove sections**: remove a section from the template lineup - **Reorder sections**: drag-and-drop to change the section order - **Publish/Unpublish**: control whether the template is available for use in page types and page creation Only published sections appear in the section picker when editing a template. --- ## Page Type Editing Page types combine structure and metadata: - **Assign a template**: select a published template that defines the section lineup - **Add reference fields**: add element fields (same types available as in sections) that serve as page-level metadata — e.g., a "Category" option, a "Featured Image" media field, or a "Related Page" reference - **Publish/Unpublish**: control whether the page type is available when creating new pages When a content editor creates a page and assigns a page type, the page is pre-populated with the template's sections and the reference fields become available as page-level configuration. --- # Page Builder Source: https://docs.brease.io/docs/page-builder The Builder is where content editors fill in page content. While the Editor defines what a page _can_ contain, the Builder is where actual text, images, links, and other data are entered for each page. --- ## Overview The workflow is straightforward: 1. Select a page from the page list 2. Manage which sections the page contains 3. Click a section to open its content editor and fill in element values --- ## Section Management Once a page is selected, you can manage its sections: ### Adding Sections Use the dropdown selector to pick from available published sections and add them to the page. Sections are added to the page's section list and become available for content editing. ### Removing Sections Remove a section from the page. This deletes the section and any content entered for it on this page. ### Reordering Sections Drag-and-drop sections to change their display order on the page. The order here determines the order in which sections are rendered on the frontend via `BreasePage`. ### Publish/Unpublish Sections Individual sections on a page can be published or unpublished. Unpublished sections are not included in the API response when the page is fetched, so they won't render on the frontend. ### Show/Hide Sections Toggle section visibility without removing the section. Hidden sections retain their content but are excluded from the API response — useful for temporarily hiding a section without losing its data. ### Global Sections Global sections are created in the **Editor** (not the Builder). When adding sections to a page, global sections appear alongside regular sections in the section picker. Once added, you can edit a global section's content directly in the Builder — but unlike regular sections, changes to a global section's content apply to **every page** that includes it. This is useful for content blocks that should be identical everywhere, such as a CTA banner, an announcement bar, or a newsletter signup form. See [Editor — Global Sections](/docs/editor#global-sections) for how to create them. --- ## Content Editing Click a section (from the kiosk or the section list) to open the **content editor drawer**. The drawer displays all elements defined in that section's schema, each rendering a type-specific input: - **String**: single-line text field - **Long Text**: multi-line textarea - **Rich Text**: CKEditor rich text editor with formatting toolbar - **Integer / Decimal**: numeric input fields - **Boolean**: toggle switch - **DateTime**: date and time picker - **Media**: media picker for selecting uploaded files (images, video, etc.) - **Link**: link picker with support for internal pages, external URLs, media files, and collection entries - **Collection**: selector for linking to collection entries - **Option**: dropdown or checkbox group based on the defined option values - **Reference**: reference to other entities - **JSON**: raw JSON editor - **Location**: location/coordinates input Fill in each field and save. Values are stored per section, per page, per locale. --- ## Live Preview When a site has a `previewDomain` configured in its settings, the Builder displays a live **iframe preview** alongside the content editor. ### How It Works - The preview loads your frontend application in an iframe pointed at the preview domain - As you edit content in the Builder, changes are streamed to the iframe in real time via the **PostMessage protocol** - The frontend receives content updates and re-renders sections without a page reload ### Preview Toolbar Inside the preview iframe, each section displays a toolbar with an **Edit** button. Clicking "Edit" jumps you back to the Builder's content editor drawer for that specific section, creating a seamless back-and-forth editing experience. {% callout title="Preview requires frontend integration" type="note" %} For live preview to work, your Next.js application must include the `BreasePreviewListener` component and `SectionToolbar` components from `brease-next`. See [Preview Integration](/docs/preview-integration) for setup instructions. {% /callout %} --- ## Locale Switching The Builder includes a **locale dropdown** that controls which locale's content you are editing. - Switching locales reloads the content editor with that locale's data - Saving content applies only to the currently selected locale - Only **localized** elements show different values per locale. Elements marked as non-localized in the Editor share a single value across all locales (e.g., an image field that should display the same image regardless of language). This allows content editors to manage translations directly within the Builder without switching to a separate tool. --- # Collection Manager Source: https://docs.brease.io/docs/collection-manager The Manager is where content editors create and manage collection entries. While the Editor defines the collection schema, and the Builder handles page content, the Manager is where repeatable data — FAQs, team members, testimonials, products — is actually authored. --- ## Overview The workflow: 1. Select a collection from the dropdown (all published collections are listed) 2. Browse the entry list, which shows all entries with drag-and-drop ordering 3. Click an entry to open the content editor and fill in its element values --- ## Entry Management ### Creating Entries Click "Create" and provide a name for the new entry. The entry is created with empty content, ready to be filled in. ### Editing Content Click an entry to open the content editor. The editor displays the same type-specific inputs as the Builder — text fields, rich text editors, media pickers, and so on — based on the collection's element schema. ### Duplicating Entries Duplicate an entry to create a copy with the same content. Useful for creating variations of similar entries without starting from scratch. ### Publishing and Unpublishing Each entry can be individually published or unpublished. Unpublished entries are excluded from API responses, so they won't appear on the frontend when fetching collection data. ### Show/Hide Toggle entry visibility without deleting or unpublishing. Hidden entries retain their content and published status but are excluded from API responses — useful for temporarily removing an entry from display. ### Deleting Entries Permanently remove an entry and all its content across all locales. ### Reordering Entries Drag-and-drop entries in the list to change their order. The ordering is persisted to the backend and reflected in API responses, so the order you set here is the order your frontend receives. --- ## Content Editing The content editing experience mirrors the Builder: - Each element in the collection schema renders its type-specific input - Content is saved **per locale** — switching locales shows different content for the same entry - **Save** syncs all changes to the backend for the currently selected locale {% callout title="Saving is per locale" type="note" %} When you save an entry, only the content for the currently selected locale is persisted. Switch to each locale individually to enter and save translated content. {% /callout %} --- ## Locale Switching The Manager includes a **locale dropdown** for switching between locales: - Switching locales reloads the entry list and entry content for the selected locale - Each locale has independent content values for every element in every entry - Entry names and ordering are shared across locales, but element content is locale-specific This allows content editors to manage all translations for a collection's entries within a single interface. --- # Getting Started Source: https://docs.brease.io/docs/getting-started This guide walks you through setting up `brease-next` in a Next.js project. {% callout title="Reading these docs with an AI agent?" %} Point your coding tool at [`/llms.txt`](/llms.txt) for a Markdown index of the whole documentation, or grab any page as raw Markdown by appending `.md` to its URL (e.g. [`/docs/getting-started.md`](/docs/getting-started.md)). {% /callout %} ## Prerequisites Before you begin, make sure you have: - **Next.js 13+** (App Router) - **React 18+** - **Node.js 16+** - A Brease CMS account with a site created ## Quick Start with CLI The fastest way to get started is the CLI scaffolding tool: ```bash npx brease-next ``` This interactive command creates a fully configured project with your CMS credentials, section mapping, and routing already wired up. See the [CLI Scaffolding](/docs/cli) page for details. If you prefer manual setup, continue below. --- ## Step 1: Install the Package ```bash npm install brease-next ``` ## Step 2: Get Your API Credentials You need two values from the Brease CMS dashboard: 1. Sign in at [app.brease.io](https://app.brease.io) 2. Open your site and navigate to **Site Settings** 3. In the **API Tokens** section, generate a new token (or copy an existing one) 4. Copy the **Environment ID** from the same settings page ![Site Settings - API Credentials](/images/img-2.png) ## Step 3: Configure Environment Variables Create a `.env.local` file in your project root: ```env BREASE_TOKEN=your_api_token_here BREASE_ENV=your_environment_id_here BREASE_DEFAULT_LOCALE=en ``` ### Environment Variables Reference | Variable | Required | Default | Description | | -------------------------- | -------- | ------- | ------------------------------------------- | | `BREASE_TOKEN` | Yes | -- | API authentication token from Site Settings | | `BREASE_ENV` | Yes | -- | Environment ID from Site Settings | | `BREASE_DEFAULT_LOCALE` | Yes | -- | Default locale code (e.g. `en`, `sk`) | | `BREASE_REVALIDATION_TIME` | No | `30` | ISR revalidation interval in seconds | ## Step 4: Configure Next.js Update your `next.config.ts` to support Brease media and redirects: ```typescript 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', })) }, } export default nextConfig ``` ## Step 5: Create a Cached Page Fetcher Create a shared, deduplicated page fetcher using React's `cache()`: ```typescript // src/lib/brease/get-page.ts import { cache } from 'react' import { fetchPage } from 'brease-next' export const getPage = cache(async (slug: string) => fetchPage(slug)) ``` This ensures that your layout, `generateMetadata`, and page component all share a single request per render instead of making redundant API calls. ## Step 6: Create a Section Map Create a configuration file that maps CMS section keys to React components: ```typescript // src/lib/brease/brease-config.ts import { ComponentType } from 'react' import HeroSection from '@/sections/hero-section' import TextSection from '@/sections/text-section' export const sectionMap: Record< string, ComponentType> > = { hero: HeroSection, text: TextSection, } export const contextConfig = { navigations: [ // { key: 'header', id: 'your-navigation-uuid' }, // { key: 'footer', id: 'your-navigation-uuid' }, ], collections: [ // { key: 'faqs', id: 'your-collection-uuid' }, ], userParams: {}, } ``` ## Step 7: Set Up Root Layout Wrap your application with `BreaseContext` in the root layout. The `slug` prop tells the context which page is currently active: ```typescript // src/app/[[...slug]]/layout.tsx import { BreaseContext } from 'brease-next' import { contextConfig } from '@/lib/brease/brease-config' import { getPage } from '@/lib/brease/get-page' import 'brease-next/styles' export default async function SlugLayout({ children, params, }: { children: React.ReactNode params: Promise<{ slug?: string[] }> }) { const { slug } = await params const pageSlug = slug ? slug.join('/') : '' return ( {children} ) } ``` Key points: - The `slug` prop accepts the current route slug, optionally with a locale prefix (e.g. `sk/about-us`). - The `getPage` prop passes your cached fetcher so the context can share page data with the page component. - `import 'brease-next/styles'` loads the styles required for CMS preview toolbar functionality. ## Step 8: Create the Catch-All Page ```typescript // src/app/[[...slug]]/page.tsx import { BreasePage, generateBreasePageMetadata, ensureSuccess } from 'brease-next' import { sectionMap } from '@/lib/brease/brease-config' import { getPage } from '@/lib/brease/get-page' import { notFound } from 'next/navigation' import type { Metadata } from 'next' type Props = { params: Promise<{ slug?: string[] }> } export async function generateMetadata({ params }: Props): Promise { const { slug } = await params const pageSlug = slug ? slug.join('/') : '' const result = await getPage(pageSlug) if (!result.success) return {} return generateBreasePageMetadata(result.data) } export default async function Page({ params }: Props) { const { slug } = await params const pageSlug = slug ? slug.join('/') : '' const result = await getPage(pageSlug) if (!result.success) { if (result.status === 404) return notFound() throw new Error(result.error) } return } ``` ## Verification Start the development server and check for errors: ```bash npm run dev ``` Open your browser to `http://localhost:3000`. If everything is configured correctly: - The page should render without console errors - You should see your CMS content (or an empty page if no sections are configured yet) If you see errors about missing environment variables, double-check your `.env.local` file and restart the dev server. ## Next Steps - [API Reference](/docs/api-reference) -- all available functions and types - [Components](/docs/components) -- BreasePage, BreaseImage, BreaseLink, and more - [Context & Hooks](/docs/context-and-hooks) -- BreaseContext configuration and useBrease() - [SEO & Metadata](/docs/seo-and-metadata) -- metadata, robots.txt, sitemap, structured data - [CLI Scaffolding](/docs/cli) -- automated project setup --- # API Reference Source: https://docs.brease.io/docs/api-reference Complete reference for all `brease-next` exports: functions, types, components, and utilities. ## Import All public exports are available from the main package entry: ```typescript import { // Functions fetchSite, fetchPage, fetchAllPages, fetchCollectionById, fetchEntryById, fetchNavigation, fetchAlternateLinks, fetchLocales, fetchRedirects, generateBreasePageParams, generateBreasePageMetadata, generateBreaseRobots, generateSitemap, validateBreaseConfig, ensureSuccess, // Components BreasePage, BreaseImage, BreaseLink, BreaseStructuredData, BreaseCustomCode, // Context & Hooks BreaseContext, useBrease, // Error class BreaseFetchError, } from 'brease-next' ``` Types can be imported separately: ```typescript import type { BreaseSection, SectionElementProps, BreaseMedia, BreaseMediaVariant, BreaseNavigation, BreaseNavigationItem, BreaseLinkData, BreasePageType, BreaseSite, BreaseConfig, BreaseCollection, BreaseCollectionEntry, BreaseResponse, BreaseContextConfig, BreaseGetPage, GenerateBreaseRobotsOptions, } from 'brease-next' ``` --- ## Configuration ### validateBreaseConfig Reads and validates environment variables. Returns a `BreaseConfig` object. Throws if any required variable is missing. ```typescript function validateBreaseConfig(): BreaseConfig ``` **Environment variables read:** | Variable | Required | Default | |---|---|---| | `BREASE_TOKEN` | Yes | -- | | `BREASE_ENV` | Yes | -- | | `BREASE_DEFAULT_LOCALE` | Yes | -- | | `BREASE_BASE_URL` | No | `"https://api.brease.io"` | | `BREASE_REVALIDATION_TIME` | No | `30` | **Returns:** `BreaseConfig` ```typescript interface BreaseConfig { baseUrl: string token: string env: string defaultLocale: string revalidationTime: number } ``` **Example:** ```typescript import { validateBreaseConfig } from 'brease-next' const config = validateBreaseConfig() console.log(config.defaultLocale) // "en" console.log(config.revalidationTime) // 30 ``` --- ## Page Functions ### fetchPage Fetches a single page by slug. The slug can include a locale prefix (e.g. `sk/about-us`), and the locale is derived from the slug automatically. ```typescript function fetchPage(pageSlug: string): Promise> ``` **Parameters:** | Name | Type | Description | |---|---|---| | `pageSlug` | `string` | Page slug, optionally prefixed with locale (e.g. `"about-us"`, `"sk/about-us"`) | **Returns:** `Promise>` **Example:** ```typescript import { fetchPage, ensureSuccess } from 'brease-next' // Default locale const result = await fetchPage('about-us') if (result.success) { console.log(result.data.name) console.log(result.data.sections.length) } // With locale prefix const skResult = await fetchPage('sk/o-nas') // Using ensureSuccess to throw on error const page = ensureSuccess(await fetchPage('about-us')) ``` --- ### fetchAllPages Fetches all page slugs for a given locale. Used primarily for static generation. ```typescript function fetchAllPages(locale: string): Promise> ``` **Parameters:** | Name | Type | Description | |---|---|---| | `locale` | `string` | Locale code (e.g. `"en"`, `"sk"`) | **Returns:** `Promise>` **Example:** ```typescript import { fetchAllPages } from 'brease-next' const result = await fetchAllPages('en') if (result.success) { console.log('Total pages:', result.data.length) result.data.forEach(page => console.log(page.slug)) } ``` --- ### fetchAlternateLinks Fetches alternate language links for a page, used for hreflang tags. ```typescript function fetchAlternateLinks( pageSlug: string ): Promise>> ``` **Parameters:** | Name | Type | Description | |---|---|---| | `pageSlug` | `string` | Page slug | **Returns:** `Promise>>` -- compatible with Next.js `metadata.alternates.languages` **Example:** ```typescript import { fetchAlternateLinks } from 'brease-next' const result = await fetchAlternateLinks('about-us') if (result.success) { // result.data can be passed directly to metadata.alternates.languages console.log(result.data) } ``` --- ## Collection Functions ### fetchCollectionById Fetches a collection and all its entries by collection ID and locale. ```typescript function fetchCollectionById( collectionId: string, locale: string ): Promise> ``` **Parameters:** | Name | Type | Description | |---|---|---| | `collectionId` | `string` | Collection UUID | | `locale` | `string` | Locale code | **Returns:** `Promise>` **Example:** ```typescript import { fetchCollectionById, ensureSuccess } from 'brease-next' const result = await fetchCollectionById('col-a01c8223-4e4a-40aa-90d9-70149e87322c', 'en') if (result.success) { console.log('Collection:', result.data.name) result.data.entries.forEach(entry => { console.log(entry.uuid, entry.name) }) } ``` --- ### fetchEntryById Fetches a single collection entry by its UUID. ```typescript function fetchEntryById( collectionId: string, entryId: string, locale: string ): Promise> ``` **Parameters:** | Name | Type | Description | |---|---|---| | `collectionId` | `string` | Collection UUID | | `entryId` | `string` | Entry UUID | | `locale` | `string` | Locale code | **Returns:** `Promise>` **Example:** ```typescript import { fetchEntryById, ensureSuccess } from 'brease-next' const result = await fetchEntryById( 'col-a01c8223-4e4a-40aa-90d9-70149e87322c', 'entry-uuid-here', 'en' ) if (result.success) { const entry = result.data console.log(entry.uuid, entry.name) console.log(entry.elements) } ``` --- ## Navigation & Site Functions ### fetchNavigation Fetches a navigation structure by ID and locale. ```typescript function fetchNavigation( navigationId: string, locale: string ): Promise> ``` **Parameters:** | Name | Type | Description | |---|---|---| | `navigationId` | `string` | Navigation UUID | | `locale` | `string` | Locale code | **Returns:** `Promise>` **Example:** ```typescript import { fetchNavigation } from 'brease-next' const result = await fetchNavigation('nav-a01c4cbb-21f7-46d5-a89c-564307998128', 'en') if (result.success) { result.data.items.forEach(item => { console.log(item.label, item.value) if (item.isExternal) { console.log('External link:', item.value) } // Nested children item.children.forEach(child => { console.log(' ', child.label) }) }) } ``` --- ### fetchSite Fetches site-level information. ```typescript function fetchSite(): Promise> ``` **Returns:** `Promise>` **Example:** ```typescript import { fetchSite } from 'brease-next' const result = await fetchSite() if (result.success) { console.log(result.data.name) console.log(result.data.domain) console.log(result.data.hasMultiLocale) console.log(result.data.sitemapIndexing) } ``` --- ### fetchRedirects Fetches all redirect rules configured in the CMS. ```typescript function fetchRedirects(): Promise> ``` **Returns:** `Promise>` **Example:** ```typescript import { fetchRedirects } from 'brease-next' const result = await fetchRedirects() if (result.success) { result.data.forEach(redirect => { console.log(redirect.source, '->', redirect.destination, `(${redirect.type})`) }) } ``` Typically used in `next.config.ts`: ```typescript import { fetchRedirects } from 'brease-next/server' const nextConfig = { async redirects() { const result = await fetchRedirects() if (!result.success) return [] return result.data.map((r) => ({ source: r.source, destination: r.destination, permanent: r.type === '301' || r.type === '308', })) }, } ``` --- ### fetchLocales Fetches all configured locales for the site. ```typescript function fetchLocales(): Promise> ``` **Returns:** `Promise>` ```typescript interface BreaseLocale { uuid: string code: string name: string isDefault: boolean } ``` **Example:** ```typescript import { fetchLocales } from 'brease-next' const result = await fetchLocales() if (result.success) { result.data.forEach(locale => { console.log(locale.code, locale.name, locale.isDefault ? '(default)' : '') }) } ``` --- ## Static Generation ### generateBreasePageParams Generates static params for all pages across all locales. Iterates every locale and fetches all pages for each. ```typescript function generateBreasePageParams(): Promise<{ locale: string; slug: string[] }[]> ``` **Returns:** `Promise<{ locale: string; slug: string[] }[]>` **Example:** ```typescript // src/app/[[...slug]]/page.tsx import { generateBreasePageParams } from 'brease-next' export async function generateStaticParams() { return generateBreasePageParams() // Returns: // [ // { locale: "en", slug: ["about-us"] }, // { locale: "en", slug: ["contact"] }, // { locale: "sk", slug: ["o-nas"] }, // { locale: "sk", slug: ["kontakt"] }, // ] } ``` --- ## SEO Functions ### generateBreasePageMetadata Generates a Next.js `Metadata` object from a `BreasePage` object. Note: this takes a **page object**, not a slug string. ```typescript function generateBreasePageMetadata( page: BreasePage, options?: { metadataBase?: string | URL } ): Metadata ``` **Parameters:** | Name | Type | Description | |---|---|---| | `page` | `BreasePage` | The page object (fetched via `fetchPage`) | | `options` | `{ metadataBase?: string \| URL }` | Optional. Base URL for resolving relative URLs | **Returns:** Next.js `Metadata` object **Generated fields:** - `title` -- from `page.metaTitle`, falls back to `page.name` - `description` -- from `page.metaDescription` - `robots` -- respects `page.indexing` toggle - `alternates` -- canonical URL from `page.canonicalUrl`, hreflang from `page.alternateLinks` - `openGraph` -- from `page.openGraph` fields - `twitter` -- from `page.twitterCard` fields **Example:** ```typescript import { fetchPage, generateBreasePageMetadata, type Metadata } from 'brease-next' export async function generateMetadata(): Promise { const result = await fetchPage('about-us') if (!result.success) return {} return generateBreasePageMetadata(result.data, { metadataBase: 'https://example.com', }) } ``` --- ### generateBreaseRobots Generates a `robots.txt` configuration object. ```typescript function generateBreaseRobots( siteUrl: string, options?: GenerateBreaseRobotsOptions ): MetadataRoute.Robots ``` **Parameters:** | Name | Type | Description | |---|---|---| | `siteUrl` | `string` | Your site's public URL | | `options` | `GenerateBreaseRobotsOptions` | Optional overrides | ```typescript interface GenerateBreaseRobotsOptions { rules?: MetadataRoute.Robots['rules'] sitemap?: string | string[] host?: string } ``` **Default behavior:** Allows all crawlers, sets sitemap to `{siteUrl}/sitemap.xml`. **Example:** ```typescript // src/app/robots.ts import { generateBreaseRobots } from 'brease-next' import type { MetadataRoute } from 'next' export default function robots(): MetadataRoute.Robots { return generateBreaseRobots('https://example.com') } ``` --- ### generateSitemap Fetches sitemap data from the CMS API. ```typescript function generateSitemap(): Promise> ``` **Returns:** `Promise>` **Example:** ```typescript // src/app/sitemap.ts import { generateSitemap, ensureSuccess } from 'brease-next' import type { MetadataRoute } from 'next' export default async function sitemap(): Promise { const result = await generateSitemap() return result.success ? result.data : [] } ``` --- ## Error Handling ### ensureSuccess Unwraps a `BreaseResponse`, returning the data on success or throwing a `BreaseFetchError` on failure. ```typescript function ensureSuccess(result: BreaseResponse): T ``` **Parameters:** | Name | Type | Description | |---|---|---| | `result` | `BreaseResponse` | Any Brease API response | **Returns:** `T` -- the unwrapped data **Throws:** `BreaseFetchError` if `result.success` is `false` **Example:** ```typescript import { fetchPage, ensureSuccess } from 'brease-next' // Throws BreaseFetchError if the request failed const page = ensureSuccess(await fetchPage('about-us')) console.log(page.name) ``` --- ### BreaseFetchError Error class thrown by `ensureSuccess` and other internal operations when an API request fails. ```typescript class BreaseFetchError extends Error { status: number endpoint?: string } ``` **Properties:** | Name | Type | Description | |---|---|---| | `message` | `string` | Error description | | `status` | `number` | HTTP status code | | `endpoint` | `string \| undefined` | The API endpoint that failed | **Example:** ```typescript import { fetchPage, ensureSuccess, BreaseFetchError } from 'brease-next' try { const page = ensureSuccess(await fetchPage('nonexistent')) } catch (error) { if (error instanceof BreaseFetchError) { console.error(`API error ${error.status}: ${error.message}`) console.error('Endpoint:', error.endpoint) } } ``` --- ## Response Type ### BreaseResponse\ Discriminated union returned by all fetch functions. Check `success` before accessing data. ```typescript type BreaseResponse = | { success: true; data: T; status: number } | { success: false; error: string; status: number; endpoint?: string } ``` **Pattern:** ```typescript const result = await fetchPage('about-us') if (result.success) { // result.data is T (BreasePage in this case) console.log(result.data.name) } else { // result.error is string console.error(result.error, result.status) } ``` --- ## Type Definitions ### BreasePage ```typescript interface BreasePage { name: string | null slug: string | null uuid: string | null indexing: boolean variables: any | null customCode: string | null structuredData: object[] | null openGraph: { url: string | null type: string | null image: string | null title: string | null description: string | null } twitterCard: { site: string | null type: string | null image: string | null title: string | null creator: string | null description: string | null } canonicalUrl: string | null metaTitle: string | null metaDescription: string | null references: object[] | null alternateLinks: Record | null parentPageSlug: string sections: BreaseSection[] } ``` ### BreaseSection ```typescript interface BreaseSection { name: string page_section_uuid: string key: string uuid: string elements: SectionElementProps } ``` ### SectionElementProps ```typescript type SectionElementProps = Record ``` ### BreaseMedia ```typescript interface BreaseMedia { alt: string | null duration: number | null extension: string height: number mimeGroup: string mimeType: string name: string path: string size: string thumbnail: string uuid: string width: number variants: Record } ``` ### BreaseMediaVariant ```typescript interface BreaseMediaVariant { alt: string | null extension: string height: number mimeType: string path: string size: string type: string width: number } ``` ### BreaseNavigation ```typescript interface BreaseNavigation { name: string uuid: string description: string | null items: BreaseNavigationItem[] } ``` ### BreaseNavigationItem ```typescript interface BreaseNavigationItem extends BreaseLinkData { uuid: string children: BreaseNavigationItem[] } ``` ### BreaseLinkData ```typescript interface BreaseLinkData { label: string isExternal: boolean value: string target: '_blank' | '_self' | null } ``` ### BreaseSite ```typescript interface BreaseSite { uuid: string name: string title: string | null domain: string published: boolean status: string hasMultiLocale: boolean | null sitemapIndexing: boolean customCode: string | null } ``` ### BreaseCollection ```typescript interface BreaseCollection { uuid: string name: string description: string | null status: string entries: BreaseCollectionEntry[] } ``` ### BreaseCollectionEntry ```typescript interface BreaseCollectionEntry { uuid: string name: string elements: Record } ``` ### BreaseRedirect ```typescript interface BreaseRedirect { uuid: string source: string destination: string type: '301' | '302' | '307' | '308' } ``` ### BreaseLocale ```typescript interface BreaseLocale { uuid: string code: string name: string isDefault: boolean } ``` ### BreaseContextConfig ```typescript interface BreaseContextConfig { navigations: Array<{ key: string; id: string }> collections?: Array<{ key: string; id: string }> userParams: any } ``` ### BreaseGetPage ```typescript type BreaseGetPage = (slug: string) => Promise> ``` ### GenerateBreaseRobotsOptions ```typescript interface GenerateBreaseRobotsOptions { rules?: MetadataRoute.Robots['rules'] sitemap?: string | string[] host?: string } ``` --- ## Next Steps - [Components](/docs/components) -- BreasePage, BreaseImage, BreaseLink, and more - [Context & Hooks](/docs/context-and-hooks) -- BreaseContext and useBrease() - [SEO & Metadata](/docs/seo-and-metadata) -- metadata generation, robots, sitemap --- # Components Source: https://docs.brease.io/docs/components The `brease-next` package exports five React components for rendering CMS content. | Component | Type | Purpose | |---|---|---| | `BreasePage` | Client | Renders page sections using a component map | | `BreaseImage` | Client | Responsive image rendering with variant support | | `BreaseLink` | Client | Smart internal/external link handling | | `BreaseStructuredData` | Server | JSON-LD structured data injection | | `BreaseCustomCode` | Server | Custom HTML/script injection | --- ## BreasePage Client component that maps CMS page sections to React components and renders them in order. ### Import ```typescript import { BreasePage } from 'brease-next' ``` ### Props | Prop | Type | Required | Description | |---|---|---|---| | `page` | `BreasePage` | Yes | The page object from `fetchPage` | | `sectionMap` | `Record>>` | Yes | Maps section keys to React components | ### How It Works 1. Iterates through `page.sections` 2. For each section, looks up the component from `sectionMap` using `section.key` 3. Spreads `section.elements` as props to the matched component 4. Renders with `section.uuid` as the React key 5. If no matching component is found for a section key, it is skipped When rendered inside a `BreaseContext`, the component automatically uses the page from context if available. This enables live preview mode where the CMS pushes page updates via PostMessage, and BreasePage re-renders with the new content without a page reload. ### Usage ```typescript // src/lib/brease/brease-config.ts import { ComponentType } from 'react' import HeroSection from '@/sections/hero-section' import FeaturesSection from '@/sections/features-section' import ContactSection from '@/sections/contact-section' export const sectionMap: Record>> = { hero: HeroSection, features: FeaturesSection, contact: ContactSection, } ``` ```typescript // src/app/[[...slug]]/page.tsx import { BreasePage, ensureSuccess } from 'brease-next' import { sectionMap } from '@/lib/brease/brease-config' import { getPage } from '@/lib/brease/get-page' import { notFound } from 'next/navigation' export default async function Page({ params }: { params: Promise<{ slug?: string[] }> }) { const { slug } = await params const pageSlug = slug ? slug.join('/') : '' const result = await getPage(pageSlug) if (!result.success) { if (result.status === 404) return notFound() throw new Error(result.error) } return } ``` ### Section Components All section components **must** include `'use client'` at the top of the file. `BreasePage` is a client component and renders sections within a client component tree — server components cannot be used in the section map. Each section component receives `section.elements` spread as props. Type your props based on the elements you configured in the CMS: ```typescript 'use client' import { BreaseMedia, BreaseLinkData } from 'brease-next' interface HeroSectionProps { title: string subtitle: string heroImage: BreaseMedia ctaButton: BreaseLinkData } export default function HeroSection({ title, subtitle, heroImage, ctaButton }: HeroSectionProps) { return (

{title}

{subtitle &&

{subtitle}

}
) } ``` --- ## BreaseImage Wraps the Next.js `Image` component with Brease media data. Supports responsive variant-based srcSet and named variant selection. ### Import ```typescript import { BreaseImage } from 'brease-next' ``` ### Props | Prop | Type | Required | Description | |---|---|---|---| | `breaseImage` | `BreaseMedia` | Yes | The media object from CMS elements | | `variant` | `'sm' \| 'md' \| 'lg' \| 'xl' \| '2xl' \| 'hd' \| 'original'` | No | Render a specific variant instead of responsive srcSet | | `alt` | `string` | No | Override the alt text from the media object | | `sizes` | `string` | No | The `sizes` attribute for responsive images | | `...rest` | any Next.js Image props | No | All other props are passed to the underlying `Image` component | ### Variant Behavior When **no variant** is specified, the component generates a responsive srcSet from all available variants. The variants are ordered by size: `sm`, `md`, `lg`, `xl`, `2xl`, `hd`, `original`. When a **specific variant** is provided, only that variant is rendered. ### Usage **Responsive (automatic srcSet):** ```typescript import { BreaseImage, type BreaseMedia } from 'brease-next' interface HeroProps { heroImage: BreaseMedia } export default function HeroSection({ heroImage }: HeroProps) { return ( ) } ``` **Specific variant:** ```typescript ``` **With alt text override:** ```typescript ``` ### Null Handling Returns `null` if `breaseImage` is falsy, so you can safely pass optional media elements without conditional rendering. --- ## BreaseLink Smart link component that renders internal links with Next.js `Link` and external links with a native `
` tag. ### Import ```typescript import { BreaseLink } from 'brease-next' ``` ### Props | Prop | Type | Required | Description | |---|---|---|---| | `linkData` | `BreaseLinkData` | Yes | Link data object from CMS elements | | `children` | `ReactNode` | Yes | Link content | | `...rest` | anchor/Link props | No | Additional props passed to the underlying element | ### BreaseLinkData Type ```typescript interface BreaseLinkData { label: string // Display label (you can use children instead) isExternal: boolean // Whether the link points to an external URL value: string // The URL or path target: '_blank' | '_self' | null } ``` ### Behavior - **Internal links** (`isExternal: false`): rendered with Next.js `` for client-side navigation - **External links** (`isExternal: true`): rendered with a native `` tag - When `target` is `'_blank'`, `rel="noopener noreferrer"` is automatically added for security ### Usage ```typescript import { BreaseLink, type BreaseLinkData } from 'brease-next' interface CtaSectionProps { heading: string ctaLink: BreaseLinkData } export default function CtaSection({ heading, ctaLink }: CtaSectionProps) { return (

{heading}

{ctaLink.label}
) } ``` **With custom children:** ```typescript -> Read more ``` --- ## BreaseStructuredData Server component that renders JSON-LD structured data from `page.structuredData` as a ` ``` ### Setting up structured data in the CMS 1. Open a page in the Brease editor 2. Navigate to the page's SEO settings 3. Add structured data entries using the JSON-LD editor 4. Choose from common schemas (Organization, Article, Product, FAQ, etc.) or write custom JSON-LD {% callout title="Validate your structured data" type="note" %} Use [Google's Rich Results Test](https://search.google.com/test/rich-results) to verify your JSON-LD output. Errors in structured data will not break rendering but will prevent rich results in search engines. {% /callout %} --- ## BreaseCustomCode Renders page-level custom code defined in the CMS. This is typically used for analytics snippets, tracking pixels, chat widgets, or inline HTML/CSS that applies to a specific page. ### Usage ```tsx import { BreaseCustomCode } from 'brease-next' export default async function Page({ params }) { const { slug } = await params const slugStr = (slug ?? []).join('/') const page = ensureSuccess(await getPage(slugStr)) return ( <> ) } ``` ### How it works - Reads `page.customCode`, which contains raw HTML/script content defined in the CMS - Renders the content using `dangerouslySetInnerHTML` - Returns `null` if `page.customCode` is `null`, `undefined`, or empty ### Common use cases - Page-specific analytics events - Embedded widgets (calendars, booking forms) - Conversion tracking pixels - Custom CSS overrides for a single page --- ## Full Page Component Example Here is a complete page component with both structured data and custom code: ```typescript // 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' export async function generateStaticParams() { return generateBreasePageParams() } 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', }) } 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 ( <> ) } ``` --- ## Component Placement Summary | Component | Data source | Where to place | | ---------------------- | --------------------- | -------------------------------------------------- | | `BreaseStructuredData` | `page.structuredData` | Before or after `BreasePage` in the page component | | `BreaseCustomCode` | `page.customCode` | After `BreasePage` in the page component | Both `BreaseStructuredData` and `BreaseCustomCode` are safe to include unconditionally. They render nothing when their data is empty. --- ## Next Steps - [Deployment](/docs/deployment) — production checklist including SEO verification - [Pages & Dynamic Routes](/docs/pages-and-routes) — the page component that hosts these components - [Media & Images](/docs/media-patterns) — Open Graph images in metadata --- # Deployment Source: https://docs.brease.io/docs/deployment This page covers building for production, configuring hosting platforms, ISR revalidation, robots.txt, sitemaps, and a pre-deployment checklist. --- ## Building for Production Run the standard Next.js build: ```bash npm run build ``` During the build, Next.js will: 1. Call `generateStaticParams()` to get all page paths across all locales 2. Pre-render every page by fetching content from the Brease API 3. Fetch and configure redirects from `next.config.ts` 4. Generate static HTML, CSS, and JS bundles Then start the production server: ```bash npm start ``` {% callout title="Build requires API access" type="warning" %} The build process fetches all content from Brease. Make sure `BREASE_TOKEN` and `BREASE_ENV` are available as environment variables during the build step on your hosting platform. {% /callout %} --- ## Incremental Static Regeneration (ISR) Pages are revalidated on a timer controlled by the `BREASE_REVALIDATION_TIME` environment variable (in seconds). The default is `30`. | Value | Behavior | |---|---| | `30` (default) | Pages revalidate every 30 seconds after first request | | `60` | Revalidate every minute | | `0` | Disable time-based revalidation (on-demand only) | | `false` | Disable revalidation entirely (fully static) | When a page is requested after the revalidation window, Next.js serves the cached version immediately and regenerates the page in the background. The next request receives the updated content. --- ## Environment Variables All hosting platforms need these environment variables: | Variable | Required | Description | |---|---|---| | `BREASE_TOKEN` | Yes | API authentication token | | `BREASE_ENV` | Yes | Environment UUID | | `BREASE_DEFAULT_LOCALE` | Yes | Default locale code (e.g. `en`) | | `BREASE_REVALIDATION_TIME` | No | ISR revalidation in seconds (default `30`) | --- ## Deploying to Vercel Vercel is the recommended hosting platform for Next.js projects. ### 1. Set environment variables In your Vercel project dashboard, go to **Settings > Environment Variables** and add: - `BREASE_TOKEN` - `BREASE_ENV` - `BREASE_DEFAULT_LOCALE` - `BREASE_REVALIDATION_TIME` (optional) Set them for **Production**, **Preview**, and **Development** environments as needed. ### 2. Deploy Push to your git repository. Vercel automatically builds and deploys: ```bash git push origin main ``` Vercel handles the Node.js runtime, ISR, and edge caching automatically. --- ## Deploying to Other Platforms For platforms like Railway, Render, AWS, or self-hosted servers: ### Requirements - Node.js 18+ runtime - Environment variables set in the platform's configuration - Ability to run `npm run build` and `npm start` ### Deploy steps ```bash # Install dependencies npm ci # Build the application npm run build # Start the production server npm start ``` The production server defaults to port 3000. Configure the `PORT` environment variable if your platform requires a different port. --- ## Robots.txt Use `generateBreaseRobots` to create a `robots.txt` route based on your site configuration: ```typescript // src/app/robots.ts import { generateBreaseRobots } from 'brease-next/server' import type { MetadataRoute } from 'next' export default function robots(): MetadataRoute.Robots { return generateBreaseRobots('https://example.com') } ``` `generateBreaseRobots` accepts a site URL and an optional options object: ```typescript const robots = generateBreaseRobots('https://example.com', { // Additional options can be passed here }) ``` --- ## Sitemap Use `generateSitemap` to create a dynamic sitemap from all published pages: ```typescript // src/app/sitemap.ts import { generateSitemap } from 'brease-next/server' import type { MetadataRoute } from 'next' export default async function sitemap(): Promise { const result = await generateSitemap() if (!result.success) { return [] } return result.data } ``` `generateSitemap` returns a `BreaseResponse` containing URLs for all published pages across all locales. --- ## CORS and Preview Mode If you use Brease's live preview feature (where the CMS editor iframes your site), configure Content Security Policy headers to allow framing from the CMS domain: ```typescript // next.config.ts const nextConfig = { async headers() { return [ { source: '/(.*)', headers: [ { key: 'Content-Security-Policy', value: "frame-ancestors 'self' https://app.brease.io", }, ], }, ] }, // ... other config } ``` {% callout title="Restrict frame-ancestors in production" type="warning" %} Only allow framing from domains you trust. The example above allows the Brease CMS (`app.brease.io`) to embed your site for preview purposes. Do not use `frame-ancestors *` in production. {% /callout %} --- ## Redirects Redirects are fetched from the Brease API at build time and configured in `next.config.ts`: ```typescript // next.config.ts import { fetchRedirects } from 'brease-next/server' const nextConfig = { 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', })) }, } ``` Redirects are baked into the build output. To update redirects after changing them in the CMS, trigger a new build or redeploy. --- ## Deployment Checklist Before going live, verify each item: ### Environment - [ ] `BREASE_TOKEN` is set on the hosting platform - [ ] `BREASE_ENV` is set and points to the production environment - [ ] `BREASE_DEFAULT_LOCALE` is set to the correct default locale - [ ] `BREASE_REVALIDATION_TIME` is set (or using the default of 30s) ### Configuration - [ ] `next.config.ts` has `remotePatterns` for the Brease media CDN - [ ] `next.config.ts` has the `redirects()` function using `fetchRedirects` - [ ] `robots.ts` is configured with `generateBreaseRobots` - [ ] `sitemap.ts` is configured with `generateSitemap` ### CMS - [ ] Preview domain is set in the Brease CMS settings (if using live preview) - [ ] All pages are published in the target environment - [ ] Structured data is configured for key pages (homepage, about, etc.) ### Build - [ ] `npm run build` completes without errors - [ ] All expected pages are pre-rendered (check build output) - [ ] No missing section key warnings in the build log - [ ] Images load correctly from the CDN ### Verification - [ ] Homepage renders correctly - [ ] Navigation links work (internal and external) - [ ] Multi-locale pages load the correct translations - [ ] Redirects work as configured in the CMS - [ ] Meta tags and Open Graph data appear in page source - [ ] Structured data validates in Google's Rich Results Test - [ ] 404 pages return the correct status code --- ## Next Steps - [Project Setup Guide](/docs/project-setup) — review your initial configuration - [Structured Data & Custom Code](/docs/structured-data-patterns) — verify SEO output - [Multi-Locale Sites](/docs/locale-patterns) — ensure all locales deploy correctly --- # Examples Source: https://docs.brease.io/docs/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 ```typescript 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 export const sectionMap: Record> = { 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 ```typescript 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 ```typescript 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 (
{children}