Data Interface
Custom layouts receive minimal props from the route handler and load their own data. This gives you complete control over the data schema.
What Layouts Receive
Unlike docs and blog post layouts, custom layouts receive only a path — no pre-rendered content:
| Received | Type | Description |
|---|---|---|
dataPath |
String | Absolute path to YAML file |
baseUrl |
String | Page URL (for context) |
Route Handler ([...slug].astro)
─────────────────────────────────
1. Reads site.yaml config
2. Gets dataPath from config
3. Passes to layout:
• dataPath: "/path/to/home.yaml" ← Just a path
• baseUrl: "/"
│
▼
Layout (home)
─────────────
• Receives: dataPath (not content!)
• Must: loadFile(dataPath) to get YAML data
• Must: Render everything from scratch
Key point: Custom layouts receive NO pre-processed content. The layout is fully responsible for loading data and rendering.
Props Interface
All custom layouts receive just two props:
interface CustomLayoutProps {
dataPath: string; // Absolute path to YAML file
baseUrl: string; // Page URL (for context)
}
That's it! The layout is responsible for:
- Loading the YAML file
- Defining the expected data shape
- Handling missing or invalid data
Loading Data
Use the loadFile helper to load YAML content:
import { loadFile } from '@loaders/data';
const { dataPath } = Astro.props;
const fileContent = await loadFile(dataPath);
const pageData = fileContent.data;
Return Structure
loadFile returns:
interface LoadedFile {
data: Record<string, unknown>; // Parsed YAML content
content?: string; // If has markdown body
filePath: string; // Absolute path
}
Defining Data Schemas
TypeScript Interface
Define what your layout expects:
// home layout
interface HeroData {
title: string;
subtitle?: string;
cta?: {
label: string;
href: string;
};
secondaryCta?: {
label: string;
href: string;
};
}
interface FeatureData {
icon?: string;
title: string;
description: string;
}
interface HomePageData {
hero?: HeroData;
features?: FeatureData[];
}
Type Casting
Cast the loaded data to your interface:
const { dataPath } = Astro.props;
const content = await loadFile(dataPath);
const pageData = content.data as HomePageData;
Example: home Layout
YAML File (home.yaml)
hero:
title: "Build Beautiful Docs"
subtitle: "A modern documentation framework"
cta:
label: "Get Started"
href: "/docs"
secondaryCta:
label: "GitHub"
href: "https://github.com/..."
features:
- icon: "🚀"
title: "Fast"
description: "Lightning quick builds"
- icon: "🧩"
title: "Modular"
description: "Pick your layout"
- icon: "⚡"
title: "Simple"
description: "Easy configuration"
Layout Component
---
import Hero from '../../components/hero/default/Hero.astro';
import Features from '../../components/features/default/Features.astro';
import { loadFile } from '@loaders/data';
import '../../components/hero/default/styles.css';
import '../../components/features/default/styles.css';
interface HeroData {
title: string;
subtitle?: string;
cta?: { label: string; href: string };
secondaryCta?: { label: string; href: string };
}
interface FeatureData {
icon?: string;
title: string;
description: string;
}
interface Props {
dataPath: string;
}
const { dataPath } = Astro.props;
let pageData: { hero?: HeroData; features?: FeatureData[] } = {};
try {
const content = await loadFile(dataPath);
pageData = content.data as typeof pageData;
} catch (error) {
console.error('Error loading home page data:', error);
}
const hero = pageData.hero;
const features = pageData.features || [];
---
<div class="home">
{hero && <Hero hero={hero} />}
{features.length > 0 && <Features features={features} />}
</div>
Example: info Layout
YAML File (about.yaml)
title: "About Us"
description: |
We are a team of developers passionate about creating
great documentation experiences.
Our mission is to make documentation easy and beautiful.
Layout Component
---
import Content from '../../components/content/default/Content.astro';
import { loadFile } from '@loaders/data';
import '../../components/content/default/styles.css';
interface Props {
dataPath: string;
}
const { dataPath } = Astro.props;
let pageData: { title?: string; description?: string } = {};
try {
const content = await loadFile(dataPath);
pageData = content.data as typeof pageData;
} catch (error) {
console.error('Error loading info page data:', error);
}
const title = pageData.title || 'Page';
const description = pageData.description;
---
<Content title={title} description={description}>
<slot />
</Content>
Mixed Content (YAML + Markdown)
YAML files can include markdown content:
YAML with Body
title: "Privacy Policy"
lastUpdated: "2024-01-15"
---
# Privacy Policy
We take your privacy seriously...
## Data Collection
We collect the following data:
- Usage analytics
- Error reports
Accessing Markdown Content
const content = await loadFile(dataPath);
const pageData = content.data; // { title, lastUpdated }
const markdownBody = content.content; // "# Privacy Policy..."
Rendering Markdown
If you need to render the markdown body:
import { marked } from 'marked';
const htmlContent = marked.parse(content.content || '');
<div class="content">
<Fragment set:html={htmlContent} />
</div>
Data Validation
Optional Validation
Add runtime validation for safety:
function validateHomeData(data: unknown): HomePageData {
if (!data || typeof data !== 'object') {
return { hero: undefined, features: [] };
}
const obj = data as Record<string, unknown>;
return {
hero: obj.hero as HeroData | undefined,
features: Array.isArray(obj.features) ? obj.features : [],
};
}
const pageData = validateHomeData(content.data);
Default Values
Always provide fallbacks:
const hero = pageData.hero;
const features = pageData.features || [];
const title = pageData.title || 'Untitled Page';
Complex Data Structures
Nested Objects
# pricing.yaml
plans:
- name: "Starter"
price: 0
features:
- "5 projects"
- "Basic support"
cta:
label: "Start Free"
href: "/signup"
- name: "Pro"
price: 29
features:
- "Unlimited projects"
- "Priority support"
- "Custom domain"
cta:
label: "Get Pro"
href: "/signup?plan=pro"
popular: true
TypeScript Interface
interface Plan {
name: string;
price: number;
features: string[];
cta: {
label: string;
href: string;
};
popular?: boolean;
}
interface PricingPageData {
plans: Plan[];
}
Error Handling
Graceful Degradation
---
let pageData = {};
let loadError = false;
try {
const content = await loadFile(dataPath);
pageData = content.data;
} catch (error) {
console.error('Failed to load page data:', error);
loadError = true;
}
---
{loadError ? (
<div class="error">
<p>Failed to load page content.</p>
</div>
) : (
<div class="home">
{/* Normal content */}
</div>
)}
Missing Data Sections
{hero ? (
<Hero hero={hero} />
) : (
<div class="hero-placeholder">
<h1>Welcome</h1>
</div>
)}