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.

Islands in the layout, and navigation without reloading the header

// src/layout.tsx — the island lives outside data-nexa-slot,
// survives every navigation
<header>
    <div data-nexa-island="userMenu" data-nexa-strategy="visible">
        <p>Loading…</p>
    </div>
</header>
<div data-nexa-slot></div>

An event of the layout's own still makes the build fail (its ids would share a manifest with each page's) — but an island CAN live there, and it survives SPA navigation intact: a user dropdown with real state, for example. Also, once a project uses a layout, navigating between pages no longer replaces the whole <body>: only the content inside data-nexa-slot changes, so the layout's header/nav (and its island, if it has one) never gets torn down between one page and the next.