Skip to content
DocPensieve
Documentation menu

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. tags show at the bottom of the page, below the text they describe — one tag may be written without brackets. Everything is optional: without title, the project name stands in.

The fields are detailed in the reference.

Who wrote the page

A page that names its authors, or carries a date, opens with a byline:

---
title: Installation
authors: [ada, grace]
date: 2026-09-09
modified: 2026-09-16
---

The names show as written. To give them a biography, an avatar and a link, describe them in a JSON file of the version folder:

{
  "ada": {
    "name": "Ada Lovelace",
    "bio": "Wrote the first algorithm meant for a machine.",
    "avatar": "authors/ada.png",
    "url": "https://example.com/ada"
  }
}

Then name that file in the configuration: authors: 'authors.json'. It is read in each version folder, as the menu is — a biography corrected in the version being written leaves the published one alone. The file itself is not published; the avatars are, and are measured at the build so that the text does not jump when they arrive. A version without the file shows the names alone: an older version needs no copy of it.

A key nobody describes is shown as written, which is what lets a project name its authors before describing them. The description also feeds the page data: a biography becomes the description of its Person, a link its url.

A date written the day the page was is not repeated as an update, and a home page carries no byline at all: it is an entrance hall, not a document.

What the URL depends on

The file path gives the URL path, stripped of its extension and of its sorting prefix:

FileURL
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

Series of pages

A folder is a series: its index page introduces it, and the pages beside that index are its instalments. Written in that index, one line builds the grid of its pages, as clickable cards:

<Cards />

Each card takes the title and description of its page, its preview as an image, and its modified date. On the index of a folder that holds other folders, the cards of those series also count their pages. Nothing is listed by hand, so nothing goes stale when a page is added or renamed.

The Cards page details every option.

Writing the menu by hand

When the file tree does not give the menu you want, describe it in a JSON file of the version's folder, and name it in the configuration:

// docpensieve.config.mjs
sidebar: 'sidebar.json',
[
  "/",
  { "label": "Start here", "items": ["guide/installation", "guide/first-site"] },
  { "auto": "components" },
  { "label": "Repository", "href": "https://github.com/me/my-project" }
]

A page is named by its path, as in its URL. { "auto": "components" } keeps the automatic menu of a folder — the DocPensieve section stays whole that way. A page the file leaves out is still published, only off the menu, and a version without the file keeps the menu of its folders — an older version needs no copy of it. Every kind of entry is in the sidebar reference.

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:

![Pipeline diagram](./diagram.png)

Files that are not pages are copied as is into the output, at the same relative place. The path is rewritten like a link.

The build reads the width and height of each image from its file — PNG, JPEG, GIF, WebP or SVG — and writes them on the page: the browser keeps the room before the image arrives, instead of shifting the text when it does. Every image but the first loads lazily, when the reader nears it; the first, often in view, keeps its normal loading.

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.

globalComponents: false removes them from every page. It is meant for a project that would rather bring its own: a page still using one then stops the build, naming it.

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.