Routing System
The framework uses a single dynamic route to handle all pages, with layout resolution based on page configuration.
Single Entry Point
All routes flow through src/pages/[...slug].astro:
/ → slug = undefined (home)
/docs → slug = ["docs"]
/docs/getting-started → slug = ["docs", "getting-started"]
/docs/getting-started/intro → slug = ["docs", "getting-started", "intro"]
/blog → slug = ["blog"]
/blog/hello-world → slug = ["blog", "hello-world"]
Route Resolution Flow
┌──────────────────────────────────────────────────────────────┐
│ URL: /docs/getting-started/overview │
└──────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ Step 1: Match Page Config │
│ ───────────────────────── │
│ pages.docs.base_url: "/docs" ✓ │
│ Remaining path: getting-started/overview │
└──────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ Step 2: Select Parser │
│ ──────────────────── │
│ Content type: docs → DocsParser │
└──────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ Step 3: Resolve Content Path │
│ ──────────────────────────── │
│ data/docs/getting-started/XX_overview.md │
└──────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ Step 4: Parse Content │
│ ───────────────────── │
│ Preprocessors → Render → Postprocessors │
└──────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ Step 5: Resolve Layout │
│ ────────────────────── │
│ @docs/doc_style1 → layouts/docs/styles/doc_style1/ │
└──────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ Step 6: Render │
│ ───────────────── │
│ Layout.astro with content, sidebar, pagination │
└──────────────────────────────────────────────────────────────┘
Page Configuration
Define pages in site.yaml:
pages:
# Documentation
docs:
type: docs
base_url: "/docs"
data: "@data/docs"
layout: "@docs/doc_style1"
# Blog
blog:
type: blog
base_url: "/blog"
data: "@data/blog"
layout: "@blogs/blog_style1"
# Home page
home:
type: custom
base_url: "/"
data: "@data/pages/home.yaml"
layout: "@custom/home"
# About page
about:
type: custom
base_url: "/about"
data: "@data/pages/about.yaml"
layout: "@custom/info"
URL Structure
Documentation URLs
File: data/docs/getting-started/01_overview.md
URL: /docs/getting-started/overview
└─┬─┘ └───────┬───────┘ └───┬───┘
base_url folder clean slug
The 01_ prefix is stripped from the URL, used only for ordering.
Blog URLs
File: data/blog/2024-01-15-hello-world.md
URL: /blog/hello-world
└─┬─┘ └─────┬─────┘
base_url clean slug (date stripped)
Custom Page URLs
Config: base_url: "/about"
URL: /about
Custom pages use exact URL matching.
Layout Resolution
Layout aliases map to filesystem paths:
Documentation Layouts
@docs/doc_style1
↓
src/layouts/docs/styles/doc_style1/Layout.astro
Blog Layouts
@blogs/blog_style1
↓
src/layouts/blogs/styles/blog_style1/IndexLayout.astro (for /blog)
src/layouts/blogs/styles/blog_style1/PostLayout.astro (for /blog/*)
Custom Page Layouts
@custom/home
↓
src/layouts/custom/styles/home/Layout.astro
Layout Validation
Missing layouts trigger build errors:
[CONFIG ERROR] Docs layout "nonexistent" does not exist.
Page: docs
Config: @docs/nonexistent
Expected: src/layouts/docs/styles/nonexistent/Layout.astro
Available: doc_style1, doc_style2
Static Path Generation
At build time, getStaticPaths() generates all routes:
export async function getStaticPaths() {
const paths = [];
const pages = await getPages();
for (const [name, config] of Object.entries(pages)) {
switch (config.type) {
case 'docs':
// Load all docs and create path for each
const docs = await loadContent(config.data, 'docs');
for (const doc of docs) {
paths.push({
params: { slug: buildSlug(config.base_url, doc.slug) },
props: { page: name, content: doc }
});
}
break;
case 'blog':
// Blog index
paths.push({
params: { slug: config.base_url.split('/').filter(Boolean) },
props: { page: name, isIndex: true }
});
// Individual posts
const posts = await loadContent(config.data, 'blog');
for (const post of posts) {
paths.push({
params: { slug: buildSlug(config.base_url, post.slug) },
props: { page: name, content: post }
});
}
break;
case 'custom':
// Single custom page
paths.push({
params: { slug: config.base_url.split('/').filter(Boolean) },
props: { page: name }
});
break;
}
}
return paths;
}
Index Pages
Documentation Index
/docs behavior:
- If
index.mdexists indata/docs/, render it - Otherwise, redirect to first doc in first section
Blog Index
/blog renders IndexLayout.astro showing all posts:
/blog → IndexLayout.astro (list of posts)
/blog/my-post → PostLayout.astro (single post)
Section Index
Folder index files (index.md) are optional:
- If present: Renders as the section landing page
- If absent: Section shows as non-clickable header in sidebar
Content Type Routing
| Type | URL Pattern | Content Source | Parser |
|---|---|---|---|
docs |
/base/*/** |
Folder of .md files |
DocsParser |
blog |
/base/* |
Folder of dated .md files |
BlogParser |
custom |
/base (exact) |
Single YAML/JSON file | - |
Alias Resolution
Path aliases are resolved at build time:
| Alias | Resolves To |
|---|---|
@data/ |
dynamic_data/data/ |
@assets/ |
dynamic_data/assets/ |
@config/ |
dynamic_data/config/ |
@docs/ |
src/layouts/docs/styles/ |
@blogs/ |
src/layouts/blogs/styles/ |
@custom/ |
src/layouts/custom/styles/ |
Props Passed to Layouts
Documentation Layout
interface DocsLayoutProps {
content: LoadedContent; // Current page content
sidebar: SidebarItem[]; // Navigation tree
pagination: {
prev?: { title: string; slug: string };
next?: { title: string; slug: string };
};
settings: ContentSettings; // Folder settings
}
Blog Layout
// Index
interface BlogIndexProps {
posts: LoadedContent[]; // All blog posts
settings: ContentSettings;
}
// Post
interface BlogPostProps {
content: LoadedContent; // Current post
settings: ContentSettings;
}
Custom Layout
interface CustomLayoutProps {
data: Record<string, unknown>; // YAML/JSON data
}
404 Handling
Unmatched routes use Astro's default 404.
Create src/pages/404.astro for custom 404 page:
---
import BaseLayout from '@layouts/BaseLayout.astro';
---
<BaseLayout title="Page Not Found">
<h1>404 - Page Not Found</h1>
<p>The page you're looking for doesn't exist.</p>
<a href="/">Go home</a>
</BaseLayout>
Dynamic Imports
Layouts are loaded using Vite's import.meta.glob:
const layouts = import.meta.glob('/src/layouts/**/*.astro');
async function getLayout(alias: string) {
const path = resolveLayoutAlias(alias);
const loader = layouts[path];
if (!loader) {
throw new Error(`Layout not found: ${alias}`);
}
return (await loader()).default;
}
This enables:
- Build-time validation of all layouts
- Tree-shaking of unused layouts
- Type-safe layout props