Layouts
Layouts wrap pages with shared UI like headers, footers, and navigation. Mage Pages supports nested layouts that compose from root to leaf.
Creating a Layout
Create _layout.tsx in any directory:
import type { LayoutProps } from "@mage/app/pages";
export default function Layout(props: LayoutProps) {
return (
<div>
<header>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
</header>
<main>{props.children}</main>
<footer>Copyright 2024</footer>
</div>
);
}
The layout receives LayoutProps with a children prop containing the page
content.
Rendering Children
Layouts must render props.children or page content will be lost:
// Correct - renders children
export default function Layout(props: LayoutProps) {
return (
<div>
<nav>...</nav>
{props.children}
</div>
);
}
// Wrong - children not rendered, page content lost
export default function Layout(props: LayoutProps) {
return (
<div>
<nav>...</nav>
</div>
);
}
Nested Layouts
Layouts nest automatically based on directory structure. Each layout wraps its children:
pages/
├── _layout.tsx # Root layout (header/footer)
├── index.md
└── docs/
├── _layout.tsx # Docs layout (sidebar)
├── index.md
└── api/
├── _layout.tsx # API layout (version selector)
└── reference.md
For /docs/api/reference, the composition order is:
RootLayout
└── DocsLayout
└── ApiLayout
└── PageContent
Each layout wraps the next, creating a nested structure.
Accessing Frontmatter
Use useFrontmatter() to access page metadata in layouts:
import type { LayoutProps } from "@mage/app/pages";
import { useFrontmatter } from "@mage/app/pages/hooks";
export default function DocsLayout(props: LayoutProps) {
const { title, description } = useFrontmatter();
return (
<div className="docs-container">
<aside>
<nav>
<h2>Documentation</h2>
{/* Navigation items */}
</nav>
</aside>
<article>
<h1>{title}</h1>
{description && <p className="lead">{description}</p>}
{props.children}
</article>
</div>
);
}
Custom frontmatter fields are also available:
const { author, category, tags } = useFrontmatter();
Layout Patterns
Documentation Layout
Sidebar navigation with table of contents:
import type { LayoutProps } from "@mage/app/pages";
import { useFrontmatter } from "@mage/app/pages/hooks";
const navItems = [
{ label: "Introduction", href: "/docs" },
{ label: "Getting Started", href: "/docs/getting-started" },
{ label: "API Reference", href: "/docs/api" },
];
export default function DocsLayout(props: LayoutProps) {
return (
<div className="flex">
<aside className="w-64 p-4">
<nav>
{navItems.map((item) => (
<a key={item.href} href={item.href} className="block py-2">
{item.label}
</a>
))}
</nav>
</aside>
<main className="flex-1 p-4">
{props.children}
</main>
</div>
);
}
Blog Layout
Author info and publish date:
import type { LayoutProps } from "@mage/app/pages";
import { useFrontmatter } from "@mage/app/pages/hooks";
export default function BlogLayout(props: LayoutProps) {
const { title, author, publishedAt } = useFrontmatter();
return (
<article className="max-w-2xl mx-auto">
<header className="mb-8">
<h1 className="text-3xl font-bold">{title}</h1>
<p className="text-gray-600">
By {author} on {new Date(publishedAt).toLocaleDateString()}
</p>
</header>
{props.children}
</article>
);
}
Marketing Layout
Different header for marketing pages:
import type { LayoutProps } from "@mage/app/pages";
export default function MarketingLayout(props: LayoutProps) {
return (
<div>
<header className="bg-gradient-to-r from-blue-500 to-purple-600 text-white">
<nav className="max-w-6xl mx-auto p-4 flex justify-between">
<a href="/" className="text-xl font-bold">Brand</a>
<div className="space-x-4">
<a href="/features">Features</a>
<a href="/pricing">Pricing</a>
<a href="/docs">Docs</a>
</div>
</nav>
</header>
{props.children}
<footer className="bg-gray-900 text-white p-8">
{/* Footer content */}
</footer>
</div>
);
}
Layout vs HTML Template
| File | Purpose | Scope |
|---|---|---|
_html.tsx |
HTML document (<html>, <head>) |
Root only |
_layout.tsx |
Page wrapper UI | Any level |
_html.tsx controls the document structure. _layout.tsx controls page UI.
Next Steps
- Components - Build interactive TSX pages
- Styling - Add CSS and UnoCSS