AvalonAvalon
GitHub

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>
}