StatsHub Docs

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 content

Create 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.

content/docs/mobile/caching.md
---
title: Caching and invalidation
description: One sentence — it feeds search, Card previews and the OG image.
icon: Database
status: new
---

Body starts here.
FieldRequiredNotes
titleyesRendered by Fumadocs. Do not repeat it as an # H1 — the page would have two
descriptionno, but write oneSearch results, <Card> previews, OG image
iconnoAny Hugeicons export name, resolved at build time. The Icon suffix is optional
statusnonew, beta, draft or deprecated; renders as a sidebar badge
fullnotrue 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.

UseWhen
.mdPorted or reference-heavy prose. < and { are ordinary characters.
.mdxYou 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.

content/docs/mobile/meta.json
{
  "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 links

The 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`.

On this page