Implementation Patterns

Navigation Patterns

Navigations in Brease are managed in the CMS and fetched automatically by BreaseContext. Use the BreaseLink component to render navigation items — it handles internal vs external links, targets, and security attributes automatically.


Accessing Navigation Data

Navigations are configured in your BreaseContextConfig and made available to client components through the useBrease() hook.

1. Register navigations in config

// src/lib/brease/config.ts
import { BreaseContextConfig } from 'brease-next'

export const contextData: BreaseContextConfig = {
  navigations: [
    { key: 'header', id: 'your-header-nav-uuid' },
    { key: 'footer', id: 'your-footer-nav-uuid' },
  ],
  collections: [],
  userParams: {},
}

2. Access in client components

'use client'

import { useBrease } from 'brease-next'

export default function Header() {
  const { navigations } = useBrease()
  const headerNav = navigations.header

  if (!headerNav) return null

  // headerNav is a BreaseNavigation object
  // headerNav.items is an array of BreaseNavigationItem
}

Each navigation item implements BreaseLinkData with added uuid and children:

interface BreaseNavigationItem {
  uuid: string
  label: string           // Display text
  isExternal: boolean     // true for external URLs
  value: string           // The full URL or path — use as href directly
  target: '_blank' | '_self' | null
  children: BreaseNavigationItem[]  // Nested items
}

The value field contains everything needed for the link — for internal links it's the path (e.g. /about), for external links it's the full URL (e.g. https://example.com). You never need to construct or manipulate it.


BreaseLink is the recommended way to render any navigation item. It reads isExternal, value, target, and label from the linkData prop and renders the correct element automatically:

  • Internal links → Next.js <Link> (client-side navigation)
  • External links → native <a> with rel="noopener noreferrer" when target="_blank"
import { BreaseLink } from 'brease-next'

<BreaseLink linkData={item} className="text-sm font-medium">
  {item.label}
</BreaseLink>

Since BreaseNavigationItem extends BreaseLinkData, navigation items can be passed directly as linkData. Any extra props (className, onClick, etc.) are forwarded to the underlying element.


Header Navigation

A complete header component with dropdown support:

// src/components/header.tsx
'use client'

import { useState } from 'react'
import { useBrease, BreaseLink } from 'brease-next'
import type { BreaseNavigationItem } from 'brease-next'
import Link from 'next/link'

export default function Header() {
  const { navigations } = useBrease()
  const headerNav = navigations.header

  if (!headerNav) return null

  return (
    <header className="border-b bg-white">
      <div className="mx-auto flex h-16 max-w-7xl items-center justify-between px-6">
        <Link href="/" className="text-xl font-bold">
          My Site
        </Link>
        <nav className="flex items-center gap-8">
          {headerNav.items.map((item) => (
            <NavItem key={item.uuid} item={item} />
          ))}
        </nav>
      </div>
    </header>
  )
}

function NavItem({ item }: { item: BreaseNavigationItem }) {
  const [open, setOpen] = useState(false)
  const hasChildren = item.children && item.children.length > 0

  if (hasChildren) {
    return (
      <div
        className="relative"
        onMouseEnter={() => setOpen(true)}
        onMouseLeave={() => setOpen(false)}
      >
        <button className="flex items-center gap-1 text-sm font-medium text-gray-700 hover:text-gray-900">
          {item.label}
          <svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
          </svg>
        </button>

        {open && (
          <div className="absolute left-0 top-full mt-1 w-48 rounded-md border bg-white py-1 shadow-lg">
            {item.children.map((child) => (
              <BreaseLink
                key={child.uuid}
                linkData={child}
                className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
              >
                {child.label}
              </BreaseLink>
            ))}
          </div>
        )}
      </div>
    )
  }

  return (
    <BreaseLink linkData={item} className="text-sm font-medium text-gray-700 hover:text-gray-900">
      {item.label}
    </BreaseLink>
  )
}

Footer navigations typically use top-level items as column headings with their children as the links:

// src/components/footer.tsx
'use client'

import { useBrease, BreaseLink } from 'brease-next'

export default function Footer() {
  const { navigations } = useBrease()
  const footerNav = navigations.footer

  if (!footerNav) return null

  return (
    <footer className="border-t bg-gray-50">
      <div className="mx-auto max-w-7xl px-6 py-12">
        <div className="grid gap-8 md:grid-cols-4">
          <div>
            <h3 className="text-lg font-bold">My Site</h3>
            <p className="mt-2 text-sm text-gray-600">
              {footerNav.description}
            </p>
          </div>

          {footerNav.items.map((group) => (
            <div key={group.uuid}>
              <h4 className="text-sm font-semibold uppercase tracking-wider text-gray-900">
                {group.label}
              </h4>
              {group.children && group.children.length > 0 && (
                <ul className="mt-4 space-y-3">
                  {group.children.map((child) => (
                    <li key={child.uuid}>
                      <BreaseLink
                        linkData={child}
                        className="text-sm text-gray-600 hover:text-gray-900"
                      >
                        {child.label}
                      </BreaseLink>
                    </li>
                  ))}
                </ul>
              )}
            </div>
          ))}
        </div>

        <div className="mt-12 border-t pt-8 text-center text-sm text-gray-500">
          &copy; {new Date().getFullYear()} My Site. All rights reserved.
        </div>
      </div>
    </footer>
  )
}

Footer navigation structure

Structure your footer navigation in the CMS with top-level items as column headings (group type) and their children as the links within each column.


Fetching Navigation Directly

If you need navigation data outside of BreaseContext (e.g. in a server component or a route that does not use the catch-all layout), fetch it directly:

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

const result = await fetchNavigation('your-nav-uuid', 'en')

if (result.success) {
  const nav = result.data
  // nav.items contains the navigation tree
}

Locale parameter required

fetchNavigation requires a locale parameter. When fetching directly (outside BreaseContext), you must supply it explicitly.


Next Steps

Previous
Collections & Entries