Skip to Content
Managing the Header & Footer

Managing the Header & Footer

There’s nothing built in. The starter kit ships zero header, footer, or navigation, confirmed by reading every relevant file directly, not inferred.

What we confirmed

components/puck/root.tsx (the Puck root config, which wraps every Puck-rendered page) has a render that does nothing but pass children through:

render: (props: { children?: ReactNode; title?: string }) => { const { children } = props; return <div className="font-sans antialiased">{children}</div>; },

Everything else in that file is SEO/social metadata fields (_meta); there’s no header/footer/nav concept anywhere in it.

app/layout.tsx (the Next.js root layout, which wraps every route including Puck-rendered ones) is equally bare:

<body data-rm-theme="light"> <div className="p1-app-shell">{children}</div> </body>

We also searched every installed @pantheon-systems/* package for a header/footer/nav concept. The only hits were SiteFooter, Navbar, SideNav, etc. in pds-toolkit-react, but those are Pantheon’s own internal design system components, used to build the P1 dashboard/editor’s chrome. Nothing there is a feature for managing a published site’s header or footer.

This means every P1 site starts as a genuinely blank canvas for site-wide chrome: no theme, no “customizer,” nothing equivalent to a WordPress theme’s header.php/footer.php or a Drupal block layout. Budget real design/build time for this on every new brand site; it’s not a checkbox to configure.

The two ways to actually build it

Option A: hardcode it in app/layout.tsx. A plain React component, written once by a developer, wraps {children}. Simplest to build, but a content editor can’t change a nav link or footer copy without a code change and a deploy.

Option B: model header/footer as their own CCR documents (recommended). Nothing in the SDK gives this to you turnkey, but the primitives fully support building it: documents can live at any path (Templates themselves already prove this: they’re just documents at _registry/templates/{name}, per Workflows). The same pattern works for site chrome:

// app/layout.tsx (Server Component) import { getPage } from "@pantheon-systems/puck-css/server"; export default async function RootLayout({ children }: { children: React.ReactNode }) { const [header, footer] = await Promise.all([ getPage("/_global/header"), getPage("/_global/footer"), ]); return ( <html lang="en"> <body> {header && <SiteHeader data={header} />} {children} {footer && <SiteFooter data={footer} />} </body> </html> ); }
  • /_global/header and /_global/footer are just documents, authored in the same Puck editor as any page: a content editor drags in a logo, a nav-links component (an array field of { label, href } pairs; see Fields Reference), footer columns, social links.
  • They go through the same draft-autosave-then-Publish flow as every other document (see Publishing), with no special-casing needed for how edits get live.
  • The catch-all route’s isInternalPath check (in app/[...puckPath]/page.tsx) already reserves /_registry and /_redirects from being treated as real pages; extend that same list to cover /_global, so /_global/header never accidentally becomes a visitable URL.

This is architecturally the same answer as templates-as-code: nothing here is a P1 platform feature, it’s a pattern built on primitives (documents, paths, getPage) the platform already exposes. Worth building once as a shared utility (a @brown-forman/p1-site-chrome package, or similar) rather than reimplementing per brand site.

What we haven’t verified

Whether /_global/* (or any reserved-path convention) is actually safe from colliding with a real content path a site manager might create, and whether the P1 dashboard’s Site Structure UI would try to list /_global/header as if it were a regular page (cluttering the page list with something that isn’t one). Worth testing directly, or asking Pantheon whether there’s an established convention for non-routable “system” documents beyond _registry/_redirects.

Last updated on