← Back to all how-tos

Reuse content across pages

Manage header and footer

Render shared header and footer entities in the app layout with dedicated PageViewer components.

Estimated time: 6 minRaw Markdown

In this how-to, you'll learn the usual React Bricks pattern for managing a shared header and footer.

Instead of embedding them inside every page, you fetch them once in the app layout and render them with dedicated <PageViewer> components.

Docs reference: Reuse content across pages

Why use dedicated PageViewers

Header and footer are layout elements shared by the whole site.

For these global fragments, it is usually better not to use embeds inside every page for performance reasons.

Instead, render them directly in your layout with:

  • one <PageViewer> for the header
  • one <PageViewer> for the footer

This is the pattern used in the starter projects scaffolded by the CLI.

Create a page type for layout entities

First, create a dedicated entity page type for layout content.

import { types } from 'react-bricks/rsc'
 
const pageTypes: types.IPageType[] = [
  {
    name: 'layout',
    pluralName: 'Layout',
    defaultStatus: types.PageStatus.Published,
    getDefaultContent: () => [],
    isEntity: true,
    allowedBlockTypes: ['header', 'footer'],
  },
]
 
export default pageTypes

This does 2 things:

  • isEntity: true puts these pages under the Entities tab
  • allowedBlockTypes ensures editors only use layout bricks there

After that, editors can create 2 entities:

  • header
  • footer

The slug names matter because the layout will fetch those pages by slug.

Minimal app/[lang]/layout.tsx example

Here is a simplified version of a starter project pattern.

import {
  PageViewer,
  cleanPage,
  fetchPage,
  getBricks,
  register,
  types,
} from 'react-bricks/rsc'
 
import config from '@/react-bricks/config'
 
import '@/app/globals.css'
 
register(config)
 
const getLayoutData = async (
  language: string
): Promise<{
  header: types.Page | null
  footer: types.Page | null
}> => {
  const [header, footer] = await Promise.all([
    fetchPage({ slug: 'header', language, config }).catch(() => null),
    fetchPage({ slug: 'footer', language, config }).catch(() => null),
  ])
 
  return { header, footer }
}
 
export default async function Layout({
  children,
  params,
}: {
  children: React.ReactNode
  params: Promise<{ lang: string }>
}) {
  const { lang } = await params
  const { header, footer } = await getLayoutData(lang)
 
  const bricks = getBricks()
  const headerOk = header
    ? cleanPage(header, config.pageTypes || [], bricks)
    : null
  const footerOk = footer
    ? cleanPage(footer, config.pageTypes || [], bricks)
    : null
 
  return (
    <html lang={lang}>
      <body>
        {headerOk ? <PageViewer page={headerOk} main={false} /> : null}
        <main>{children}</main>
        {footerOk ? <PageViewer page={footerOk} main={false} /> : null}
      </body>
    </html>
  )
}

What this layout does

The important parts are:

  • fetchPage loads the header and footer entities
  • cleanPage removes unknown or disallowed bricks before rendering
  • PageViewer renders each entity
  • main={false} tells React Bricks these viewers are not the main page content

The children route is rendered between them:

<main>{children}</main>

so every page automatically gets the same shared header and footer.

Why cleanPage is important

Before rendering a page returned by the API, the starter projects call cleanPage.

This removes content that should not be rendered, such as bricks that are unknown in the current project or not allowed by the page type configuration.

That makes the render path safer and more predictable.

If your app is not localized

This example uses app/[lang]/layout.tsx, so it receives a lang route param.

If your project is not localized, the same idea still applies.

You can just fetch with a fixed language, for example:

fetchPage({ slug: 'header', language: 'en', config })

and place the same logic in your root app/layout.tsx.

Summary

For header and footer, the usual React Bricks approach is:

  1. store them as entities
  2. fetch them in the layout
  3. render them with dedicated <PageViewer> components

Use this pattern for global layout fragments. Use embeds instead for reusable content blocks that editors place inside normal pages: Reuse a fragment (embed)