Add a docs page
Where a doc goes, what its frontmatter needs, how the sidebar is ordered, and every component and syntax available on the page.
All documentation lives in apps/statshub-docs/content/docs. There is no docs/ at the
repository root and no docs/ inside an app — that was the point of
centralising.
bun run dev:docs # localhost:3001, hot-reloads contentCreate the file
The path becomes the URL: content/docs/mobile/data.md serves at
/docs/mobile/data. A directory's index file serves at the directory's own
URL.
---
title: Caching and invalidation
description: One sentence — it feeds search, Card previews and the OG image.
icon: Database
status: new
---
Body starts here.| Field | Required | Notes |
|---|---|---|
title | yes | Rendered by Fumadocs. Do not repeat it as an # H1 — the page would have two |
description | no, but write one | Search results, <Card> previews, OG image |
icon | no | Any Hugeicons export name, resolved at build time. The Icon suffix is optional |
status | no | new, beta, draft or deprecated; renders as a sidebar badge |
full | no | true drops the table of contents and widens the page |
Quote any value containing a colon-space
description: Status: draft is invalid YAML and takes the whole build down with
a YAMLParseError that names the line but not the file.
Choose .md or .mdx
Both are picked up — fumadocs-mdx globs **/*.{mdx,md}. The choice is about
parsing, not preference.
| Use | When |
|---|---|
.md | Ported or reference-heavy prose. < and { are ordinary characters. |
.mdx | You want components: <Cards>, <Tabs>, <Accordions>, <Files> |
MDX parses < and { as JSX, so a line like Array<string> or { id }
outside a code fence is a build error in .mdx and plain text in .md. Most
reference pages here are .md for that reason. Start with .md; switching is a
rename plus whatever MDX then objects to.
The remark plugins below work in both — that is the point of :::warning
fences and Mermaid code blocks rather than JSX components.
Order it in the sidebar
A directory with no meta.json is ordered alphabetically, which is almost never
the reading order you want.
{
"title": "Mobile app",
"icon": "Smartphone",
"pages": ["index", "architecture", "data", "errors", "conventions"]
}pages holds basenames without extension. A page you leave out still builds and
still resolves by URL — it just vanishes from the sidebar, which is the quiet way
to lose a doc. "..." appends everything unlisted; use it for dated directories
like plans/ and specs/ where alphabetical is chronological. A
"---Label---" entry inserts a separator, which is how the root
content/docs/meta.json groups the top-level sections.
Verify it
bun run build:docs # icon names
bun run --filter=statshub-docs lint # internal linksThe icon resolver in src/lib/icons.tsx throws on a name that is not in
@hugeicons/core-free-icons, so a misspelled one fails the build. Absolute /docs/... hrefs are a different matter — they are
just strings in an anchor, and a wrong one renders happily and 404s when someone
clicks it. scripts/check-links.ts is what catches those, and it runs as part
of lint. Neither shows up in dev.
What you can write on the page
Callouts
:::note, :::tip, :::info, :::warning, :::danger and :::success, with
an optional title in square brackets. These work in .md as well as .mdx.
:::warning[The ledger is generated]
Edit `apps/statshub-api/docs/.ported` and run `make porting-status`.
:::Steps
A run of headings written as ## 1. Do the thing becomes a numbered step list
automatically — the numbers in this page's own headings are doing it.
Mermaid diagrams
A ```mermaid fence renders as a diagram. Mermaid is loaded lazily on the
client, so a page without one pays nothing for it.
```mermaid
flowchart LR
legacy --> contract --> api
```Maths
$...$ inline and $$...$$ display, through KaTeX. The odds and model pages
use it.
Package-install tabs
A ```package-install fence expands into npm/pnpm/yarn/bun tabs. Since this
repo is Bun-only, a plain bash fence is usually more honest.
Inlining a real file
A doc-gen:file block reads a file from the repository at build time, so the
snippet cannot drift from the code it quotes. The path is relative to the page.
```json doc-gen:file
{
"file": "../../../../../turbo.json",
"codeblock": { "lang": "json" }
}
```Type tables
An auto-type-table block builds a props table from a real TypeScript
declaration, and <TypeTable> writes one by hand.
Components in .mdx
<Cards> / <Card>, <Tabs> / <Tab>, <Accordions> / <Accordion>,
<Files> / <Folder> / <File>, <Steps> / <Step>, <Banner>,
<InlineTOC>, <ImageZoom>, <GithubInfo>, <TypeTable>,
<DynamicCodeBlock>.
apps/statshub-docs/src/components/mdx.tsx is the list that actually ships — check it
before reaching for something that may not be registered.
Code fences
title="path/to/file.ts" labels a block. Inline code can carry a language with
the trailing-colon syntax: `bun run build:docs`.