Layouts & Shared Data
Use layouts for navigation, headers, and other UI shared by several pages. Layouts nest with your directories and keep their component state as you navigate.
Layouts
Place a layout file in a pages/ directory to wrap its pages:
- 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>
</>
);
}When a page renders, the layout wraps it. The page component is injected as the layout's children in React, Solid, and Svelte, or as a slot in Vue:
Named Layouts
Named layouts let individual pages opt into a different layout without changing the URL structure. Define them in _layouts/ directories within 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
Export a layout constant from any 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, use 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.
Void uses the closest matching _layouts/ file in the page's directory or an ancestor. A missing named layout fails the build.
Shared Data
Middleware can inject data available on every page via c.set("shared", {...}). Augment CloudContextVariables to type the shared data, and useShared() will infer the type automatically:
// 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.