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/headerand/_global/footerare just documents, authored in the same Puck editor as any page: a content editor drags in a logo, a nav-links component (anarrayfield 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
isInternalPathcheck (inapp/[...puckPath]/page.tsx) already reserves/_registryand/_redirectsfrom being treated as real pages; extend that same list to cover/_global, so/_global/headernever 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.