Writing Content
How the file-based content model works and how to write MDX pages.
Content directory
All content lives in content/ at the project root. The URL path mirrors the
filesystem path exactly:
| File | URL |
|---|---|
content/index.mdx | / |
content/about.mdx | /about |
content/blog/post.mdx | /blog/post |
content/blog/ (directory) | /blog (article listing) |
Frontmatter
Every MDX file should start with a YAML frontmatter block:
---
title: My Page # required
description: A short blurb. # required
publish-date: 2026-01-15 # optional — shown in header and listing cards
last-edited: 2026-02-01 # optional — shown in header if different from publish-date
author: your name # optional
tags: [one, two] # optional
draft: true # optional — hides the file from directory listings
---last-edited falls back to the file's filesystem mtime when omitted. It is only
displayed in the article header if it differs from publish-date.
Directory listings
A directory without an index.mdx automatically renders a listing of non-draft
MDX files, sorted by publish-date descending (undated files sort last). No
configuration needed. If no articles are found the route returns 404.
The listing is non-recursive when the directory contains at least one .mdx
file directly. If the directory contains only subdirectories, the listing
becomes recursive and collects articles from the entire subtree.
To embed a listing inside any MDX file, use the ::article-list directive:
::article-list{dir=blog} ::article-list{dir=blog limit=5}Drafts
Set draft: true in frontmatter to exclude a file from directory listings and
::article-list. The page is still accessible directly by URL during
development.
MDX
Any valid Markdown is also valid MDX. You can embed JSX components anywhere in the body. The built-in components are documented in MDX Components.
Math
Inline math with $...$ and block math with $$...$$:
Euler's identity: $e^{i\pi} + 1 = 0$
$$
\int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi}
$$Code blocks
Fenced code blocks are highlighted by Shiki. Specify the language after the opening fence:
```ts
const greeting: string = 'hello';
```