Implementation Patterns

Project Setup Guide

Two paths to get a Brease-powered Next.js site running: the CLI scaffold (recommended) or manual setup.


Run the interactive scaffolding tool:

npx brease-next

The CLI walks you through the following prompts:

  1. Project name — directory name for your new app
  2. Template — choose starter (pre-built sections and layouts) or base (minimal skeleton)
  3. API token — your Brease API token from the dashboard
  4. Environment ID — the UUID of the Brease environment to connect to
  5. Default locale — e.g. en, sk, de
  6. Install dependencies — runs npm install automatically if confirmed
  7. Initialize git — runs git init if confirmed

Once complete you will have a fully configured project. Skip to the Verify Your Setup section below.


Path B: Manual Setup

1. Create a Next.js App

npx create-next-app@latest my-brease-app --typescript
cd my-brease-app

2. Install brease-next

npm install brease-next

3. Configure Environment Variables

Create .env.local in the project root:

BREASE_TOKEN=your_brease_api_token
BREASE_ENV=your_environment_uuid
BREASE_DEFAULT_LOCALE=en
BREASE_REVALIDATION_TIME=30
VariableRequiredDescription
BREASE_TOKENYesAPI authentication token from the Brease dashboard
BREASE_ENVYesEnvironment UUID (e.g. production, staging)
BREASE_DEFAULT_LOCALEYesDefault locale code (e.g. en, sk)
BREASE_REVALIDATION_TIMENoISR revalidation interval in seconds (default 30)

Keep secrets out of version control

Add .env.local to your .gitignore. Never commit API tokens to your repository.

4. Configure next.config.ts

Add remote image patterns for Brease media and wire up CMS-managed redirects:

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

const nextConfig: NextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'assets.brease.io',
        pathname: '/**',
      },
    ],
  },

  async redirects() {
    const result = await fetchRedirects()

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

    return result.data.map((redirect) => ({
      source: redirect.source,
      destination: redirect.destination,
      permanent: redirect.type === '301' || redirect.type === '308',
    }))
  },
}

export default nextConfig

5. Create the Brease Config

This file defines your section component map and context configuration:

// src/lib/brease/config.ts
import { ComponentType } from 'react'
import { SectionElementProps, BreaseContextConfig } from 'brease-next'

// Map section keys (from CMS) to React components
export const componentMap: Record<string, ComponentType<SectionElementProps>> = {
  // hero: HeroSection,
  // features: FeaturesSection,
}

// Context configuration — navigations and collections available globally
export const contextData: BreaseContextConfig = {
  navigations: [
    // { key: 'header', id: 'your-header-nav-uuid' },
    // { key: 'footer', id: 'your-footer-nav-uuid' },
  ],
  collections: [
    // { key: 'faqs', id: 'your-faq-collection-uuid' },
  ],
  userParams: {},
}

6. Create the Page Fetcher

Use React cache to deduplicate page fetches within a single render pass:

// src/lib/brease/get-page.ts
import { cache } from 'react'
import { fetchPage } from 'brease-next/server'

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

7. Create the Catch-All Layout

The layout wraps every page with BreaseContext, which provides navigation, collection, and locale data to all child components:

// src/app/[[...slug]]/layout.tsx
import { BreaseContext } from 'brease-next'
import { contextData } from '@/lib/brease/config'
import { getPage } from '@/lib/brease/get-page'

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

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

8. Create the Catch-All Page

This page handles static generation, metadata, and rendering:

// src/app/[[...slug]]/page.tsx
import { notFound } from 'next/navigation'
import {
  BreasePage,
  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 <BreasePage page={page} sectionMap={componentMap} />
}

9. Create a Sample Section Component

Section components are client components that receive their data via props. The props correspond to the element keys defined in the CMS:

// src/sections/hero-section.tsx
'use client'

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

interface HeroSectionProps {
  title: string
  subtitle: string
  backgroundImage: BreaseMedia
}

export default function HeroSection({
  title,
  subtitle,
  backgroundImage,
}: HeroSectionProps) {
  return (
    <section className="relative py-24 px-6">
      {backgroundImage && (
        <BreaseImage breaseImage={backgroundImage} variant="xl" />
      )}
      <h1 className="text-5xl font-bold">{title}</h1>
      {subtitle && <p className="mt-4 text-xl">{subtitle}</p>}
    </section>
  )
}

Then register it in your config:

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

export const componentMap = {
  hero: HeroSection as unknown as ComponentType<SectionElementProps>,
}

Section key matching

The key in componentMap (e.g. hero) must match the section's key field in the CMS, not its display name. You can find the key in the Brease dashboard under the section settings.


Verify Your Setup

Run the development server:

npm run dev

Open http://localhost:3000. If your Brease environment has published pages, you should see content rendered on screen. Check the terminal for any API errors.

You can also validate your environment variables programmatically:

import { validateBreaseConfig } from 'brease-next/server'

const config = validateBreaseConfig()
// Throws if BREASE_TOKEN, BREASE_ENV, or BREASE_DEFAULT_LOCALE are missing

Next Steps

Previous
CLI Scaffolding