Layouts and shared data
Use layouts for navigation, headers and other UI shared by several pages. Layouts nest with your folders and keep their state as you navigate.
On this pageAdd a layout · Use named layouts · Share data across pages
Layouts
This step wraps pages in a layout. Put a layout file in a pages/ directory.
- pages/
- layout.tsxRoot layout (nav, footer)
- index.tsx
- users/
- layout.tsxNested layout for /users/*
- index.tsx
- [id].tsx
// pages/layout.tsx
import { useShared, Link } from '@void/react';
export default function Layout({ children }: { children: React.ReactNode }) {
const { auth } = useShared();
return (
<>
<nav>
<Link href="/">Home</Link>
<Link href="/users">Users</Link>
{auth?.user && <span>{auth.user.name}</span>}
</nav>
<main>{children}</main>
</>
);
}<!-- pages/layout.vue -->
<script setup lang="ts">
import { useShared, Link } from '@void/vue';
const { auth } = useShared();
</script>
<template>
<nav>
<Link href="/">Home</Link>
<Link href="/users">Users</Link>
<span v-if="auth?.user">{{ auth.user.name }}</span>
</nav>
<main>
<slot />
</main>
</template><!-- pages/layout.svelte -->
<script>
import { useShared, Link } from "@void/svelte";
let { children } = $props();
const { auth } = useShared();
</script>
<nav>
<Link href="/">Home</Link>
<Link href="/users">Users</Link>
{#if auth?.user}<span>{auth.user.name}</span>{/if}
</nav>
<main>
{@render children()}
</main>// pages/layout.tsx
import { useShared, Link } from '@void/solid';
import type { JSX } from 'solid-js';
export default function Layout(props: { children: JSX.Element }) {
const shared = useShared();
return (
<>
<nav>
<Link href="/">Home</Link>
<Link href="/users">Users</Link>
{shared.auth?.user && <span>{shared.auth.user.name}</span>}
</nav>
<main>{props.children}</main>
</>
);
}The layout wraps each page it covers. The page is passed as children in React, Solid and Svelte, and as a slot in Vue.
Named layouts
Named layouts let a page use a different layout without changing its URL. This step defines them in _layouts/ directories inside pages/.
- pages/
- _layouts/
- landing.tsxnamed "landing"
- post.tsxnamed "post"
- layout.tsxdefault root layout
- index.tsx
- blog/
- hello.mdlayout: post
- archive.mdlayout: landing
- pricing.tsxlayout: !landing (exclusive)
Selecting a named layout
This step selects a named layout. Export a layout constant from the page.
export const layout = 'landing';
export default function Page() {
return <div>This page uses the landing layout</div>;
}<script>
export const layout = 'landing';
</script>
<template>
<div>This page uses the landing layout</div>
</template><script context="module">
export const layout = "landing";
</script>
<div>This page uses the landing layout</div>export const layout = 'landing';
export default function Page() {
return <div>This page uses the landing layout</div>;
}For Markdown pages, set it in frontmatter:
---
layout: post
---
# My Blog PostLayout modes
| Value | Behavior |
|---|---|
"landing" | Replace the innermost layout in the chain. Outer ancestor layouts still apply. |
"!landing" | Replace the entire chain. Only the named layout wraps the page. |
false | No layout wrapping at all. Page renders standalone. |
A named layout replaces the deepest default layout. In [root, docs/layout], it replaces docs/layout. In [root], it replaces the root.
The SDK uses the closest matching _layouts/ file in the page's directory or an ancestor. A missing named layout fails the build.
Shared data
This step shares data with every page. Middleware sets it with c.set("shared", {...}). Augment CloudContextVariables to type it, and useShared() infers the type.
// middleware/01.auth.ts
import { defineMiddleware } from 'void';
import { getUser, type AuthUser } from 'void/auth';
declare module 'void' {
interface CloudContextVariables {
shared: { auth: { user: AuthUser | null } };
}
}
export default defineMiddleware(async (c, next) => {
const user = getUser();
c.set('shared', { auth: { user } });
await next();
});Call useShared() from your framework adapter, as in the layout examples above. Its return type is inferred from CloudContextVariables. Shared data comes from middleware. Page props come from the loader. See Context variables.