Data Interface
Docs layouts receive processed content, not raw markdown. Understanding this interface is essential for creating or customizing layouts.
What Layouts Receive
The layout receives three types of data with different sources:
| Data | Received As | Source | Layout Responsibility |
|---|---|---|---|
| Current page content | Rendered HTML string (content) |
Route handler (pre-processed) | Just inject with set:html |
| Outline/TOC headings | Array of heading objects (headings) |
Route handler (pre-extracted) | Filter and render |
| Sidebar/navigation | Path to data folder (dataPath) |
Route handler passes path | Load and build tree |
Route Handler ([...slug].astro)
─────────────────────────────────
1. Loads current page markdown
2. Runs parser pipeline → HTML
3. Extracts headings during parsing
4. Passes to layout:
• content: "<h1>...</h1>" ← Already rendered HTML
• headings: [{depth, slug, text}] ← Already extracted array
• dataPath: "/path/to/docs" ← Just a path string
• title, description, etc.
│
▼
Layout (doc_style1)
───────────────────
• Page content: Just render it (already HTML)
• Outline: Filter headings array, render links
• Sidebar: Must load data using dataPath, then build tree
Summary:
- Content & Headings → Pre-processed by route handler, layout just renders
- Sidebar → Layout must load and build (route handler only passes path)
Props Interface
Every docs layout receives these props:
interface DocsLayoutProps {
// Content metadata (from frontmatter)
title: string; // Page title (required)
description?: string; // Page description
// Path information
dataPath: string; // Absolute path to docs folder
baseUrl: string; // Base URL (e.g., "/docs")
currentSlug: string; // Current page slug
// Rendered content
content: string; // HTML string (processed markdown)
headings?: Heading[]; // Extracted headings for TOC
}
interface Heading {
depth: number; // 1-6 (h1-h6)
slug: string; // URL-safe ID (e.g., "getting-started")
text: string; // Heading text content
}
Content Processing Pipeline
The content prop contains fully processed HTML, not raw markdown:
Raw Markdown Processed HTML (content prop)
───────────── ────────────────────────────
# Hello World <h1 id="hello-world">Hello World</h1>
Some **bold** text. → <p>Some <strong>bold</strong> text.</p>
[[./assets/code.py]] <pre><code>print("hello")</code></pre>
What's Already Done
By the time content reaches the layout:
| Processing | Status | Example |
|---|---|---|
| Markdown → HTML | ✅ Done | **bold** → <strong>bold</strong> |
| Heading IDs | ✅ Done | <h2> → <h2 id="slug"> |
| Asset embedding | ✅ Done | \[[./code.py]] → <pre><code>...</code></pre> |
| External links | ✅ Done | <a> gets target="_blank" |
| Custom tags | ✅ Done | <callout> → styled HTML |
| Code highlighting | ✅ Done | Syntax highlighted |
Rendering Content
Use Astro's set:html directive to render the HTML string:
---
const { content } = Astro.props;
---
<div class="content">
<Fragment set:html={content} />
</div>
Important: Never use {content} directly — it will escape the HTML.
Headings Array
The headings prop is an array of extracted headings for building table of contents:
// Example headings array
[
{ depth: 2, slug: "installation", text: "Installation" },
{ depth: 3, slug: "prerequisites", text: "Prerequisites" },
{ depth: 3, slug: "npm-install", text: "NPM Install" },
{ depth: 2, slug: "configuration", text: "Configuration" },
]
Building Outline/TOC
---
const { headings = [] } = Astro.props;
// Filter to show only h2 and h3
const tocHeadings = headings.filter(h => h.depth >= 2 && h.depth <= 3);
---
<nav class="toc">
<ul>
{tocHeadings.map(heading => (
<li class={`depth-${heading.depth}`}>
<a href={`#${heading.slug}`}>{heading.text}</a>
</li>
))}
</ul>
</nav>
Path Information
dataPath
Absolute filesystem path to the docs content folder:
const { dataPath } = Astro.props;
// "/Users/.../dynamic_data/data/docs"
Used to:
- Load all content for sidebar building
- Load folder settings
- Resolve relative asset paths
baseUrl
The URL prefix for docs pages:
const { baseUrl } = Astro.props;
// "/docs"
Used to:
- Build navigation links
- Determine current page state
- Generate pagination URLs
currentSlug
The slug of the current page (without base URL):
const { currentSlug } = Astro.props;
// "getting-started/overview"
Used to:
- Highlight current page in sidebar
- Build current URL:
${baseUrl}/${currentSlug}
Loading Additional Data
Layouts can load additional data using loaders:
Loading Sidebar Content
import { loadContentWithSettings } from '@loaders/data';
const { dataPath } = Astro.props;
const { content, settings } = await loadContentWithSettings(dataPath);
// content: Array of all docs in the folder
// settings: Object from settings.json
Building Sidebar Tree
import { buildSidebarTree, getPrevNext } from '@/hooks/useSidebar';
const sidebarNodes = buildSidebarTree(content, baseUrl, dataPath);
const { prev, next } = getPrevNext(sidebarNodes, currentPath);
Example: Full Layout Implementation
---
import { loadContentWithSettings } from '@loaders/data';
import { buildSidebarTree, getPrevNext } from '@/hooks/useSidebar';
import Sidebar from '../../components/sidebar/default/Sidebar.astro';
import Body from '../../components/body/default/Body.astro';
import Outline from '../../components/outline/default/Outline.astro';
import Pagination from '../../components/common/Pagination.astro';
interface Props {
title: string;
description?: string;
dataPath: string;
baseUrl: string;
currentSlug: string;
content: string;
headings?: { depth: number; slug: string; text: string }[];
}
const { title, description, dataPath, baseUrl, currentSlug, content, headings = [] } = Astro.props;
// Load sidebar data
const { content: allContent, settings } = await loadContentWithSettings(dataPath);
const sidebarNodes = buildSidebarTree(allContent, baseUrl, dataPath);
// Pagination
const currentPath = `${baseUrl}/${currentSlug}`;
const { prev, next } = getPrevNext(sidebarNodes, currentPath);
// Filter headings for outline
const outlineHeadings = headings.filter(h => h.depth >= 2 && h.depth <= 3);
---
<div class="docs-layout">
<Sidebar nodes={sidebarNodes} currentPath={currentPath} />
<Body title={title} description={description} content={content}>
<Pagination prev={prev} next={next} />
</Body>
{outlineHeadings.length > 0 && (
<Outline headings={outlineHeadings} />
)}
</div>
Type Definitions
For TypeScript support, import types from loaders:
import type { LoadedContent, ContentSettings, Heading } from '@loaders/data';
// LoadedContent shape:
interface LoadedContent {
id: string;
slug: string;
content: string;
headings: Heading[]; // Extracted during parsing, cached with content
data: {
title: string;
description?: string;
sidebar_label?: string;
sidebar_position?: number;
draft?: boolean;
};
filePath: string;
relativePath: string;
}
// Heading shape:
interface Heading {
depth: number; // 1-6 (h1-h6)
slug: string; // URL-safe ID
text: string; // Heading text content
}