Docs Site

Maintaining the docs site

This is the manual for the documentation site you're reading right now — how it's built, how to add and edit pages, and the handful of gotchas that will bite you if you don't know them.

The docs site lives in docs/site/ in the monorepo. It is a self-contained Next.js 15 (App Router) app that renders Markdoc (.md) pages, indexes them with FlexSearch for ⌘K / Ctrl K search, and styles them with Tailwind CSS v4. It's built on the Tailwind Plus Syntax template, fully rebranded for White Tree Digital. It is completely independent of the website/ and studio/ packages — its own package.json, its own .next, its own deploy story.

The one rule that produced these pages

Every content page was written from the live source files, not from the _DOCS.md index. The index only says where to look; the code is the source of truth. When you update a page, re-read the file it documents and end the page with a “Where this lives” pointer so the next person can verify. If a doc and the code disagree, the code wins.


Run it locally

Everything happens inside docs/site/:

cd docs/site
npm install
npm run dev      # http://localhost:3000 — hot-reloads page.md edits
npm run build    # production build; catches bad frontmatter, broken Markdoc, dead links
npm run start    # serve the production build (after npm run build)

npm run dev is what you want while writing — edits to any page.md or component hot-reload. Run npm run build before you call a change done; it's the real gate.


Add or edit a page

Adding a page is two steps: create the file, then register it in the sidebar. A page that isn't in navigation.ts still builds and is reachable by URL, but it won't appear in the nav.

1. Create src/app/docs/<slug>/page.md. The folder name is the URL slug — src/app/docs/deployment/page.md serves at /docs/deployment. The home page is the one exception: it's src/app/page.md, served at /.

Every page starts with this frontmatter:

---
title: "Sidebar + H1 title"
nextjs:
  metadata:
    title: "Browser tab / SEO title"
    description: "One-line SEO description."
---

The body follows the closing ---. Make the first paragraph the lead by appending {% .lead %} to it — it renders larger, as the page intro (that's the sentence at the top of this page).

2. Register it in src/lib/navigation.ts. The sidebar is a plain array of sections, each with a title and a list of { title, href } links. Add your page to the right section (or add a new section):

{
  title: 'Deployment & Operations',
  links: [
    { title: 'Deployment (Cloudflare)', href: '/docs/deployment' },
    { title: 'Launch & operations', href: '/docs/launch-operations' },
  ],
},

href: '/' is the home page; every other page is /docs/<slug> and must match its folder name under src/app/docs/.

Quote frontmatter values that contain a colon

Frontmatter is YAML. An unquoted scalar containing a colon-space — description: The renderer: how it works — is parsed as a nested mapping and the build fails with bad indentation of a mapping entry. Always wrap title: and description: values in double quotes. (This is why every page here has quoted frontmatter.) The same applies to values with a leading #, -, @, [, or other YAML-significant characters.


Markdoc authoring reference

Pages are Markdoc, not MDX — standard Markdown plus a small set of {% %} tags. No JSX or React in .md files. The tags are defined in src/markdoc/tags.js.

  • Lead paragraph — append {% .lead %} to the first paragraph. It renders larger, as the page intro.
  • Callouts — wrap content between an opening {% callout type="note" title="…" %} and a closing {% /callout %}. type is note (default, forest accent) or warning (amber); always close the tag. Use warning for footguns — the "this silently breaks in production" traps that are half the value of these docs. (The warning box further down this page is a live one.)
  • Quick-links — the card grid used on landing pages: wrap one or more {% quick-link title="…" icon="…" href="/docs/…" description="…" /%} items in a {% quick-links %}{% /quick-links %} pair (the home page has a live example). Valid icon values: installation, presets, plugins, theming, lightbulb, warning. To add another, drop an icon component in src/components/icons/ and register it in the icons map in src/components/Icon.tsx.
  • Figures{% figure src="/diagram.png" alt="…" caption="…" /%}. Images go in public/ (referenced as /diagram.png). There are no images today; the tag is available if you add one.
  • Code — fenced blocks with a language: ```ts, ```astro, ```groq, ```bash, ```json, ```css. The language drives syntax highlighting (theme in src/styles/prism.css). Prefer short, real snippets copied from the code over invented ones.
  • On-page table of contents## and ### headings build the right-hand "On this page" TOC automatically. Start body headings at ##.
  • Divider — a line containing only --- renders a horizontal rule between major sections.

Cross-linking. Link sibling pages with normal Markdown to their /docs/<slug> — e.g. [Deployment](/docs/deployment). Internal links are validated implicitly (a wrong slug is a dead link).

Don't invent external URLs for file references

When you cite a repo file, render it as inline code — `website/src/lib/sanity.ts`not as a link to a guessed URL. There is no public GitHub mirror to link to, so a [path](https://github.com/…) link points nowhere useful. Keep file paths as plain code; only link to real, reachable URLs (a library's repo, a live domain).


Search is zero-config. FlexSearch scans every page.md at build time and builds the index; ⌘K / Ctrl K (or the search box) opens it. New pages are searchable as soon as they're built — nothing to register. If you ever need to tune ranking or the indexed fields, the loader is src/markdoc/search.mjs.


Branding & theme

The look is White Tree Digital's dark forest-and-gold system, mapped onto the template. If you need to adjust it, here's where each piece lives:

  • Colorssrc/styles/tailwind.css. The @theme block holds the WTD brand tokens (--color-forest, --color-gold, --color-paper, --color-ground, …) and remaps the template's slate / sky / indigo ramps onto brand-tinted values, so the whole UI recolors from one place. The palette mirrors website/src/styles/global.css.
  • Accents are theme-aware. Because the brand rule is gold text never on cream, accent text is written text-forest dark:text-gold (forest on the light theme, gold on dark). Follow that pattern for any new accent — don't hard-code a single accent color.
  • Code themesrc/styles/prism.css (cream text, gold keywords, green strings on the dark code surface).
  • Fontssrc/app/layout.tsx loads Archivo (display), Figtree (body), and IBM Plex Mono (code) via next/font. The @theme --font-* tokens in tailwind.css point at them.
  • Logo & wordmark — the pine mark is src/components/Logo.tsx (Logomark); the header pairs it with the "White Tree Digital Docs" wordmark in src/components/Layout.tsx.
  • Metadata — the title template and default description are in metadata in src/app/layout.tsx.
  • Faviconsrc/app/icon.svg (the pine mark) and src/app/favicon.ico, sourced from website/public/.
  • Home pagesrc/app/page.md (the lead, the quick-links grid, and the "how this is organised" list).

Build & deploy gotchas

Never run a build while a server is using .next

npm run dev and npm run start both hold the .next directory open. Running npm run build at the same time corrupts it — you'll get runtime Cannot find module './vendor-chunks/…' errors and every route 500s. Stop the running server first, then build. If .next is already corrupted, rm -rf .next and rebuild clean. (This is the docs-site cousin of the website's "astro build clobbers the dev Vite cache" trap.)

The docs site is a standard Next.js app, so it deploys anywhere Next runs (Vercel, a Node host, or a static export if you drop the dynamic bits). It has no runtime dependency on Sanity, HubSpot, or Cloudflare — it's pure content, so a plain npm run build output is all it needs. There are no secrets and no environment variables to wire up.


Verify before you publish

Same checklist the site was built against:

  • npm run build passes (it catches broken Markdoc, bad frontmatter, and dead internal links).
  • Every sidebar link resolves, and any new page appears in the nav.
  • ⌘K / Ctrl K search returns your new page.
  • Light and dark modes both render (toggle in the header).
  • No leftover template text — a quick grep -riE 'cacheadvance|lorem|ipsum|tailwind plus' src/app src/components should come back empty.

Where this lives

All paths are relative to docs/site/.

ConcernFile
Doc pagessrc/app/docs/<slug>/page.md
Home pagesrc/app/page.md
Sidebar / navsrc/lib/navigation.ts
Markdoc tags (callout, quick-link, figure)src/markdoc/tags.js
Search loadersrc/markdoc/search.mjs
Theme tokens + ramp remapsrc/styles/tailwind.css
Code-block themesrc/styles/prism.css
Fonts + metadata + root layoutsrc/app/layout.tsx
Header, wordmark, theme togglesrc/components/Layout.tsx
Logo / pine marksrc/components/Logo.tsx
Quick-link iconssrc/components/Icon.tsx, src/components/icons/
Faviconsrc/app/icon.svg, src/app/favicon.ico
Previous
Tunables & specs