Writing pages
A page is a .md or .mdx file in the version folder. Both go through the
same chain: the extension does not change what is possible, it only states the
intent.
The frontmatter
---
title: Installation
description: What you need, and how to set up a project.
date: 2026-09-09
tags: [guide, installation]
---
title becomes the <title> tag, the menu entry and the breadcrumb.
description feeds the metadata and the JSON-LD. Everything is optional:
without title, the project name stands in.
The fields are detailed in the reference.
What the URL depends on
The file path gives the URL path, stripped of its extension and of its sorting prefix:
| File | URL |
|---|---|
index.md | / |
guide/01-installation.md | /guide/installation/ |
guide/index.md | /guide/ |
components/02-columns.mdx | /components/columns/ |
The 01- prefix orders the menu without appearing in the URL. It is the
only way to sort pages other than alphabetically, and it saves keeping a
separate list.
The menu order
The menu is derived from the file tree. Folders become sections, numeric
prefixes give the order, and an index.md in a folder provides the title of
the section.
docs/v1.0/
├── index.mdx → /
├── guide/
│ ├── index.md → /guide/ (title of the section)
│ ├── 01-installation.md
│ └── 02-first-site.md
└── components/
├── index.md
└── 01-card.mdx
Internal links
Two spellings, two meanings:
- relative —
./sibling/,../guide/— resolves against the folder of the page's file, as between any two files; - absolute —
/guide/installation/— starts from the version root, not from the domain root.
The second rule deserves a pause. A documentation does not know it may be
served under /my-project/versions/v1.0/: if /guide/installation/ were taken
literally, every internal link would break as soon as a prefix comes into play.
They are therefore rewritten at build time.
To target a real domain URL, the full address remains.
Images
An image sits next to the page and is written relatively:

Files that are not pages are copied as is into the output, at the same relative place. The path is rewritten like a link.
Components
In an .mdx page, the shipped components are used without an import:
<Columns>
<Column span={8}>The bulk of the point</Column>
<Column span={4}>A side remark</Column>
</Columns>
They are rendered at build time: the delivered HTML only holds their result. The list is in Components.
A trap to know
The content of a JSX tag left alone on its own line becomes a paragraph:
<p className="flex gap-3">Some text</p>
produces <p class="flex gap-3"><p>Some text</p></p> — two nested paragraphs,
which is invalid. The browser closes the first one by itself: the wrapper
disappears, and the intended layout with it.
The formatter makes the trap sneaky. A long string of classes ends up broken over several lines, which leaves the text alone on its line after the fact, without anyone having written it that way.
Three ways to guard against it:
- prefer
<div>to<p>as a wrapper — a paragraph is valid inside it; - write short content on the same line as its tags;
- for repeated cases, put a class in the
theme/folder rather than a long string of utilities, so that the line stays short.
docpensieve check reports these nestings on the produced site.
What is not published
A page whose frontmatter carries draft: true is loaded but kept out of the
output. That is what lets a work-in-progress page stay in the repository
without being published.
DocPensieve