Nexanexa
Step 12

Shared layouts

An optional src/layout.tsx wraps every page's HTML — header, footer, nav — without reopening component composition.

The data-nexa-slot element

// src/layout.tsx
export default function Layout() {
    return (
        <div>
            <header>My site</header>
            <div data-nexa-slot></div>
            <footer>© 2026</footer>
        </div>
    );
}

It must be empty, and there must be exactly one in the whole layout — that's where each page's already-rendered HTML gets inserted, verbatim.

Why this isn't composition

// Rust renders the layout, finds the ONE data-nexa-slot
// (must be empty), and inserts there the HTML the page
// already rendered — a text splice, not a function call.

The layout compiles just like any page. Neither the layout nor the page know about each other before that final splice.

export const head: the one thing that really reaches <head>

// src/layout.tsx
export const head = {
    icon: "/static/logo.svg",
    appleTouchIcon: "/static/icon-180.png",
    stylesheets: ["/static/site.css"]
};

export default function Layout() { /* ... */ }

Everything else the layout returns ends up inside <body> (via the splice above) — a <link rel="icon"> placed there gets stuck in <body>, where not every browser detects it reliably (this very site had exactly that bug). export const head = { icon, appleTouchIcon, stylesheets } is resolved separately and merged into the document's real <head> — same pattern as seo/schema, a literal object, never executed code.