brease-next Package

SEO & Metadata

The brease-next package provides functions and components for generating SEO metadata, robots.txt, sitemaps, structured data, and custom code injection -- all driven by data managed in the Brease CMS.


generateBreasePageMetadata

Generates a Next.js Metadata object from a BreasePage object. This is the primary way to set page-level SEO tags.

Important: This function takes a page object (the result of fetchPage), not a slug string.

Signature

function generateBreasePageMetadata(
  page: BreasePage,
  options?: { metadataBase?: string | URL }
): Metadata

Parameters

NameTypeRequiredDescription
pageBreasePageYesThe page object from fetchPage
options{ metadataBase?: string | URL }NoBase URL for resolving relative URLs in Open Graph images, canonical URLs, etc.

Generated Metadata Fields

Metadata FieldSourceFallback
titlepage.metaTitlepage.name
descriptionpage.metaDescription--
robots.indexpage.indexing--
robots.followpage.indexing--
alternates.canonicalpage.canonicalUrl--
alternates.languagespage.alternateLinks--
openGraph.urlpage.openGraph.url--
openGraph.typepage.openGraph.type--
openGraph.titlepage.openGraph.title--
openGraph.descriptionpage.openGraph.description--
openGraph.imagespage.openGraph.image--
twitter.sitepage.twitterCard.site--
twitter.cardpage.twitterCard.type--
twitter.titlepage.twitterCard.title--
twitter.descriptionpage.twitterCard.description--
twitter.creatorpage.twitterCard.creator--
twitter.imagespage.twitterCard.image--

Usage

// src/app/[[...slug]]/page.tsx
import { generateBreasePageMetadata } from 'brease-next'
import { getPage } from '@/lib/brease/get-page'
import type { Metadata } from 'next'

type Props = { params: Promise<{ slug?: string[] }> }

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { slug } = await params
  const pageSlug = slug ? slug.join('/') : ''
  const result = await getPage(pageSlug)

  if (!result.success) return {}

  return generateBreasePageMetadata(result.data, {
    metadataBase: 'https://example.com',
  })
}

generateBreaseRobots

Generates a robots.txt configuration object for the Next.js Metadata API.

Signature

function generateBreaseRobots(
  siteUrl: string,
  options?: GenerateBreaseRobotsOptions
): MetadataRoute.Robots

Parameters

NameTypeRequiredDescription
siteUrlstringYesYour site's public URL (e.g. "https://example.com")
optionsGenerateBreaseRobotsOptionsNoOverride default rules, sitemap, or host
interface GenerateBreaseRobotsOptions {
  rules?: MetadataRoute.Robots['rules']
  sitemap?: string | string[]
  host?: string
}

Default Behavior

Without options, the function generates:

  • rules: Allow all user agents on all paths
  • sitemap: {siteUrl}/sitemap.xml

Usage

// 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')
}

With custom options:

// 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', {
    rules: [
      { userAgent: '*', allow: '/' },
      { userAgent: 'Googlebot', allow: '/', disallow: '/private/' },
    ],
    sitemap: [
      'https://example.com/sitemap.xml',
      'https://example.com/blog-sitemap.xml',
    ],
    host: 'https://example.com',
  })
}

generateSitemap

Fetches sitemap data from the Brease CMS API. Returns data in the format expected by Next.js MetadataRoute.Sitemap.

Signature

function generateSitemap(): Promise<BreaseResponse<MetadataRoute.Sitemap>>

Usage

// src/app/sitemap.ts
import { generateSitemap } from 'brease-next'
import type { MetadataRoute } from 'next'

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const result = await generateSitemap()
  return result.success ? result.data : []
}

The CMS controls which pages appear in the sitemap based on the indexing toggle on each page. Pages with indexing disabled are excluded.


BreaseStructuredData

Server component that renders JSON-LD structured data from page.structuredData.

How It Works

  • Reads the structuredData field from the page object (an array of JSON-LD objects)
  • Renders each object as a <script type="application/ld+json"> tag
  • Renders nothing if structuredData is null or an empty array

Usage

import { BreaseStructuredData, ensureSuccess, fetchPage } from 'brease-next'

export default async function Page() {
  const page = ensureSuccess(await fetchPage('about-us'))

  return (
    <>
      <BreaseStructuredData page={page} />
      {/* page content */}
    </>
  )
}

Structured data is managed per page in the CMS under the page's SEO settings. Common JSON-LD schemas include:

  • Organization -- company info, logo, contact
  • Article -- blog posts, news articles
  • Product -- e-commerce product data
  • FAQ -- frequently asked questions
  • BreadcrumbList -- breadcrumb navigation

BreaseCustomCode

Server component that renders custom HTML and scripts from page.customCode.

How It Works

  • Reads the customCode field from the page object (a string of HTML/script content)
  • Renders the content using dangerouslySetInnerHTML
  • Renders nothing if customCode is null or empty

Usage

import { BreaseCustomCode, ensureSuccess, fetchPage } from 'brease-next'

export default async function Page() {
  const page = ensureSuccess(await fetchPage('about-us'))

  return (
    <>
      {/* page content */}
      <BreaseCustomCode page={page} />
    </>
  )
}

Custom code is managed per page in the CMS page settings. Use cases include page-specific analytics events, third-party widget scripts, or embedded content.

For site-wide scripts, use BreaseSite.customCode (available via fetchSite()).


Page-Level SEO Fields

These fields are managed in the CMS page editor and drive the metadata generation:

Core SEO

FieldTypeDescription
metaTitlestring | nullThe <title> tag. Falls back to page name if not set
metaDescriptionstring | nullThe meta description tag
canonicalUrlstring | nullCanonical URL for the page
indexingbooleanControls robots meta tag. When false, page is noindex/nofollow

Open Graph

FieldTypeDescription
openGraph.urlstring | nullog:url
openGraph.typestring | nullog:type (e.g. "website", "article")
openGraph.imagestring | nullog:image URL
openGraph.titlestring | nullog:title
openGraph.descriptionstring | nullog:description

Twitter Card

FieldTypeDescription
twitterCard.sitestring | nulltwitter:site handle
twitterCard.typestring | nullCard type (e.g. "summary_large_image")
twitterCard.imagestring | nulltwitter:image URL
twitterCard.titlestring | nulltwitter:title
twitterCard.creatorstring | nulltwitter:creator handle
twitterCard.descriptionstring | nulltwitter:description

Advanced

FieldTypeDescription
structuredDataobject[] | nullArray of JSON-LD objects for structured data
customCodestring | nullArbitrary HTML/script injection
alternateLinksRecord<string, string> | nullLocale-to-URL mapping for hreflang tags

Complete Setup Example

Here is a full example with all SEO features configured:

robots.ts:

// 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')
}

sitemap.ts:

// src/app/sitemap.ts
import { generateSitemap } from 'brease-next'
import type { MetadataRoute } from 'next'

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const result = await generateSitemap()
  return result.success ? result.data : []
}

Page with metadata:

// src/app/[[...slug]]/page.tsx
import {
  BreasePage,
  BreaseStructuredData,
  BreaseCustomCode,
  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<Metadata> {
  const { slug } = await params
  const pageSlug = slug ? slug.join('/') : ''
  const result = await getPage(pageSlug)

  if (!result.success) return {}

  return generateBreasePageMetadata(result.data, {
    metadataBase: 'https://example.com',
  })
}

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)
  }

  const page = result.data

  return (
    <>
      <BreaseStructuredData page={page} />
      <BreasePage page={page} sectionMap={sectionMap} />
      <BreaseCustomCode page={page} />
    </>
  )
}

Next Steps

Previous
Context & Hooks