Pages
How the files in your content folder become pages, and how to organize and name them so routing and navigation are inferred automatically.
Your docs are just a folder of Markdown and MDX files. Blume turns each file into a page — routing, navigation, and metadata are inferred from the file system, so there’s no manifest to keep in sync.
Content lives under your content root (docs/ by default; change it with content.root in blume.config.ts).
Markdown and MDX
Blume renders two kinds of file:
.md— Markdown for plain prose: GFM, frontmatter, smart punctuation, and super/subscript..mdx— everything.mdhas, plus components and the MDX-only directives, package installs, and math.
Reach for .md when a page is just prose, and .mdx when it needs components or directives. Switching is as simple as renaming the file.
In .mdx, { starts a JavaScript expression and < a JSX tag, so text that’s fine in .md can break a page: a literal brace, an HTML comment (write {/* … */}), or an element left open (write <br />). Blume parses every .mdx page when it reads the project, so blume dev, blume validate, blume doctor, and blume check report one that doesn’t parse as BLUME_MDX_SYNTAX, an error at the line and column where MDX stopped, and blume build stops there before it compiles anything. With --no-strict, the build leaves the page out and builds the rest of the site. blume dev keeps the page, and opening it shows the error.
An expression can parse and still fail when the page renders, like {user.name} on a page where nothing defines user. blume build reports that as BLUME_MDX_UNDEFINED_NAME at the expression’s line, in the page or in the partial that holds it. To show braces as text, escape each opening brace (\{) or put the text in inline code. For a value every page shares, define a variable.
Files and routes
Each file maps to a route by its path under the content root:
| File | Route |
|---|---|
docs/index.mdx |
/ |
docs/quickstart.mdx |
/quickstart |
docs/guides/theming.mdx |
/guides/theming |
docs/guides/index.mdx |
/guides |
Nested folders become nested routes, and an index.mdx inside a folder becomes that folder’s own page.
Characters that would break a page’s URL — #, ?, %, and : — are dropped from its route, so 100%.mdx publishes at /100. Keep # and ? out of file and folder names altogether: Astro’s content loader can’t read such a file, so Blume reports it as an error and leaves it out of the site. Rename sdks/c#.mdx to sdks/c.mdx and it publishes at /sdks/c, the route the characters would have been dropped from anyway.
Ordering with numeric prefixes
Prefix a file or folder with a number and a -, _, or . to control its order in the sidebar. The prefix is stripped from the URL, so you can reorder pages without breaking links:
01-introduction.mdx -> /introduction
02-installation.mdx -> /installation
A version or a date at the start of a name is part of the name, not an order: 1.2.0.mdx routes to /1.2.0, and 2024-01-05-launch.mdx to /2024-01-05-launch. Blume reads these as dates:
- An ISO date,
YYYY-MM-DD:2024-01-05.mdxroutes to/2024-01-05. - A day or month first, then a four-digit year,
D-M-YYYYorM-D-YYYYwith one or two digits for the day and month:12-05-2022.mdxroutes to/12-05-2022. - A year and a month,
YYYY-MM:2024-01.mdxroutes to/2024-01.
A date ends the name or is followed by a -, _, or ., like the rest of a name (2024-01-recap.mdx). Earlier Blume versions read the first number of the last two forms as an ordering prefix, so 12-05-2022.mdx published at /05-2022 and 2024-01.mdx at /01. Those old URLs now redirect to the new ones, unless another page lives there. Only file and folder names lose a prefix (an Obsidian vault’s notes count as files). A frontmatter slug, and a page from a content source such as a CMS or GitHub Releases, keep the name they were given.
Ordering has several layers — see Navigation for the full precedence rules.
Group folders
Wrap a folder name in parentheses to group its pages in the sidebar without adding a URL segment:
docs/(internal)/security.mdx -> /security
The pages share an “Internal” sidebar group but keep flat, parenthesis-free URLs. A group folder takes a numeric prefix like any folder, inside or outside the parentheses: (01-internal) and 01-(internal) both sort first and route like (internal).
Drafts
Mark a page as a draft to keep it out of production builds while still previewing it in blume dev:
---
title: Work in progress
draft: true
---
blume build skips drafts; blume dev renders them so you can work in the open.
Content types
Every page has a type, set with the type frontmatter field (default doc). Types let Blume treat groups of pages differently — most importantly, blog and changelog pages are collected into feeds.
---
title: v1.2.0
type: changelog
date: 2026-06-20
changelog:
version: 1.2.0
category: Features
---
The type is independent of where the file lives, but by convention blog posts go under blog/ and changelog entries under changelog/. Both get an RSS feed automatically, and changelog entries are also collected into a generated /changelog timeline. See Blog and Changelog for authoring each.
Feeds
Blume generates an RSS feed automatically for each content type listed in seo.rss.types — blog and changelog by default — as long as it has at least one page. Feeds are served at /<type>/rss.xml:
| Type | Feed |
|---|---|
blog |
/blog/rss.xml |
changelog |
/changelog/rss.xml |
Give each entry a date so items sort newest-first and carry a pubDate. An unquoted YAML date is fine — Blume normalizes it:
---
title: Introducing Blume
type: blog
date: 2026-06-22
description: Why we built a markdown-first docs framework.
---
Feeds need an absolute site URL, so set deployment.site. Blume adds <link rel="alternate"> tags to every page so browsers and feed readers discover them automatically. See Blog and Changelog for authoring each content type.
On this page
Every page gets an automatic table of contents, built from its headings. On wide screens it sits in a sticky sidebar beside your content; on narrower screens it collapses into an On this page panel above the page. As you scroll, the entry for the section you’re reading is highlighted, so you always know where you are in a long page.
Blume slugifies each heading into an anchor, so every entry links straight to its section — and you can deep-link to any heading by appending its slug to the URL (.../my-page#getting-started).
By default the contents list your ## and ### headings (H2 and H3); set toc to change the heading range or turn the table of contents off. A page with no headings in that range simply has no table of contents.