Layouts
How Avalon's layout system works, including nested layouts and skipLayouts.
What is a layout?
A layout is a wrapper component that wraps one or more pages.
Module-based apps
create-avalon sets layoutsDir to shared layouts that wrap every module. Each module can add its own nested layout under layouts/:
app/
shared/layouts/_layout.tsx ← HTML shell for the whole site
modules/
main/layouts/_layout.tsx ← optional wrapper for `/` routes
blog/layouts/_layout.tsx ← optional wrapper for `/blog/*`
See Modules for route prefixes and skipLayouts.
Root layout
app/shared/layouts/_layout.tsx (or src/layouts/_layout.tsx without modules) wraps every page:
import type { LayoutProps } from '@useavalon/avalon';
export default function RootLayout({ children, frontmatter }: LayoutProps) {
return (
<html lang="en">
<head>
<title>{frontmatter?.title ?? 'My Site'}</title>
</head>
<body>
<header>...</header>
<main>{children}</main>
</body>
</html>
);
}
Nested layouts
With modules, a layout at app/modules/blog/layouts/_layout.tsx wraps that module’s pages and nests inside the shared root layout.
Without modules in your Vite config, layouts live in src/layouts/ and nest by directory: src/layouts/blog/_layout.tsx wraps all pages under src/pages/blog/:
src/layouts/
_layout.tsx ← wraps everything
blog/
_layout.tsx ← wraps src/pages/blog/* (nested inside root)
docs/
_layout.tsx ← wraps src/pages/docs/* (nested inside root)
The nesting is automatic — inner layouts render inside the root layout's {children}.
Skipping layouts
To bypass the root layout for a specific page, export a layoutConfig object:
export const layoutConfig = {
skipLayouts: ['_layout'],
};
export default function LandingPage() {
return (
<html>
{/* Full custom HTML — no layout wrapper */}
</html>
);
}
This is useful for landing pages that need complete control over the HTML structure, including their own <html>, <head>, and navigation.
Frontmatter
Layouts receive a frontmatter prop containing metadata from the page. For MDX pages, this comes from the YAML front matter block:
---
title: My Page
description: A description for SEO
currentPath: /docs/my-page
---
For .tsx pages, export a frontmatter object:
export const frontmatter = {
title: 'My Page',
description: 'A description for SEO',
};
Layout props type
import type { LayoutProps } from '@useavalon/avalon';
export default function MyLayout({ children, frontmatter }: LayoutProps) {
// frontmatter is Record<string, unknown>
}