GenerateSaaS

Authoring Docs

Write docs as a maintained LLM-wiki - atomic, linked, linted pages in the Fumadocs app, gated by config.docs.

The docs site is apps/docs (a Fumadocs app) gated by config.docs.enabled. It renders Markdown from the repo-root /docs folder (config.docs.contentDir), so the same files appear here and ship into your generated project as AI-agent context (the generated AGENTS.md points coding agents at docs/).

Treat /docs as a maintained LLM-wiki (Karpathy's term): a graph of atomic, cross-linked pages that an AI retrieves from, not a pile of long articles. The rules below merge that discipline with how the docs are delivered (silos, components, scannable style).

Structure: one concept per page

Each page is the smallest useful unit, so a reader, human or AI, lands on exactly the concept they need.

  • One concept per page. A focused page is easier to scan and retrieves better than a grab-bag. When a page covers two things, split it (see Restructuring).
  • The path is the page's stable key. payments/credits is referenced by that path. Pick a clear, stable slug; renaming it breaks every inbound link and any backlink, so redirect when you must change it.
  • Category is the folder. Related pages share a folder (payments/, deployment/); a top-level page has no category. Folder = sidebar group = silo section.
  • Prefer many small linked pages over a few large ones. Splitting and linking beats sprawling.

The cross-reference graph: no orphans

The wiki is the links, not just the pages. A page nobody links to is invisible to a reader navigating by topic and weak for retrieval.

  • Every page is reachable. A new page must be linked from at least one related page (and link out to its neighbors), and listed in the silo meta.json. Cross-reference liberally.
  • Link instead of re-explaining. Point at the sibling page that owns a concept rather than restating it; that keeps each page atomic and the graph dense.
  • Close the loop both ways. When page A references B, make sure B (or its section) also points back where it helps a reader going the other direction.

Health: lint the wiki

After any docs change, run:

pnpm docs:lint

It fails on broken links (a cross-link or <Card href> to a missing page), orphans (a page no other page links to), and missing frontmatter (title/description). Fix every finding before you are done.

Writing style

Pages double as AI-agent context, and readers scan rather than read. Be high-impact, actionable, visual, and dense.

  • Summary first. Open with one self-contained paragraph that names the page's subject in full and says what it covers. A retrieved chunk, or a skimming reader, must understand the page from that opener alone.
  • Self-contained sections. Each ## / ### section leads with a sentence that names its subject, never leaning on "this" or "it" to refer back. An AI retrieves a section out of context, so each must stand alone.
  • Action first. Lead with the command, config, or steps; defer the background. A reader arrives to act, not to study.
  • Visual first. Lead with a table, <Cards>, <Steps>, or a <Callout>; use prose only for the connective why.
  • Maximize value per word. Every line earns its place. Cut filler ("simply", "just", "in order to").
  • Be concrete. Name the file, key, or command; link a sibling page instead of re-explaining it.
  • Complete, not thin. Cover every step the reader needs; never leave them guessing what to do next.
  • Clear over clever. Plain words, present tense, active voice.

Frontmatter

Validated against Fumadocs' pageSchema. Unlisted keys are silently dropped:

FieldRequiredDescription
titleyesH1 + sidebar label.
descriptionyesOne dense, self-contained sentence: name the subject in full and say what the page covers. Doubles as SEO, the llms.txt blurb, and the retrieval hint, so it is the single highest-value line on the page.
iconnoA Lucide PascalCase name shown in the sidebar.
fullnotrue renders the page full-width (no on-this-page TOC).
---
title: Credits
description: How the numeric credit balance is spent, metered, and granted in platform billing mode.
icon: Coins
---

The silo folder's meta.json controls its sidebar. The title labels that silo in the switcher.

{
  "title": "My Stack",
  "root": true,
  "pages": ["index", "---Getting Started---", "quick-start", "..."]
}
KeyEffect
titleFolder label, shown in the switcher dropdown.
root: trueMarks the folder a top-level section. Fumadocs builds the switcher from every root folder and isolates the sidebar to the active one.
pagesSlugs in display order.
"---Label---"A pages entry rendered as a section divider.
"..."A pages entry that expands to "everything else."
defaultOpen / collapsible / iconOptional folder display tweaks (see Fumadocs docs).

If the boilerplate ships more than one silo, the switcher flips between them with no layout code. A generated project keeps only its own silo.

Components

apps/docs/components/mdx.tsx returns the global component map (getMDXComponents): Fumadocs defaults - <Cards>/<Card>, <Callout> - plus the explicitly added <Steps>/<Step>. Use any of them in any .mdx with no import.

ComponentUse for
<Cards> / <Card>Next-step / routing grids. Doubles as graph edges out of the page.
<Callout>A genuine warning or prerequisite.
<Steps> / <Step>Ordered setup or checklists.

Register new MDX components in getMDXComponents, never with a per-file import. Because /docs lives outside apps/docs, a bare import inside a docs page fails the build.

  • Link to siblings with a silo-prefixed path: [Payments](/next/payments).
  • Never use /docs/... or a bare /payments.
  • These links resolve within whichever silo the page is viewed in - that is what lets shared backend pages live identically across silos, and they are the edges docs:lint walks to find orphans and broken links.

Add a new page

Create a .md / .mdx file under the silo folder in /docs, with title + description frontmatter.
Add its slug to pages in the folder's meta.json to place it in the sidebar.
Write the body summary-first, with self-contained sections and silo-prefixed cross-links; link it from at least one related page and link out to its neighbors so it is not an orphan.
Run pnpm docs:lint (no orphans / broken links / missing frontmatter), then pnpm --filter docs dev (default port 3040) to preview.

Restructuring a large page

When a page covers more than one concept, promote it to a collapsible folder of focused sub-pages:

Make a subfolder named for the old page slug (e.g. payments/) and move the page into it as index.mdx. This keeps the original /next/payments URL working as the folder landing page, so no redirect is needed.
Split the body into sibling .mdx files (plans-and-pricing.mdx, credits.mdx, ...), each one focused concept.
Add a folder meta.json with title, an icon, and pages in display order. The folder renders as a collapsible group; add "defaultOpen": false to ship it collapsed.
Keep the index light: a one-line intro plus a <Cards> grid linking the sub-pages, so the folder landing page is a hub in the graph rather than a wall of text.
// docs/next/payments/meta.json
{ "title": "Billing & Payments", "icon": "CreditCard", "pages": ["plans-and-pricing", "credits", "organizations"] }

Renaming or moving a page

A changed URL must never strand a reader or lose its backlinks.

  • No broken links: after any move or rename, search the whole /docs tree for the old path and update every reference - sibling pages, <Cards>, and meta.json (grep -rn "/next/old-path" docs). pnpm docs:lint catches any you miss.
  • Redirect the old URL: add a permanent (308) redirect to the closest new page so SEO and backlinks transfer. Next applies basePath automatically, so list the silo-prefixed path:
// apps/docs/next.config.ts
const nextConfig: NextConfig = {
  // ...existing config
  async redirects() {
    return [{ source: "/next/old-page", destination: "/next/section/new-page", permanent: true }];
  },
};
Splitting a page into a folder with index.mdx keeps the original URL, so it needs no redirect. Only redirect URLs that actually change.

Frequently asked questions

Why an LLM-wiki and not just docs? These pages ship into every generated project as AI-agent context. An atomic, linked, linted graph means a coding agent retrieves the one relevant page with its neighbors, instead of a long article where the answer is buried.

Why silo-prefixed links instead of [[wikilinks]]? The docs use silo-prefixed Markdown links because the same shared page renders in every framework silo and the link must resolve per-silo. The graph and its lint are the point, not the syntax.

Where do I change which folder gets read? config.docs.contentDir drives the source directory (this repo reads top-level ../../docs; generated projects default to content/docs). See Configuration.

Why doesn't my page show in the sidebar? A page only appears once its slug is listed in the silo meta.json pages array - or matched by a "..." catch-all entry.

On this page