This post is a specimen. Everything it describes, it also does, so you can open content/blog/anatomy-of-a-post.mdx next to the rendered page and match one to the other. Copy from it freely.

Only the frontmatter has rules you cannot see from the page. title, description, pubDate and category are required, excerpt and draft are optional, and a missing or malformed one fails the build with the field named.

Headings build the side panel

The panel beside this text is not authored. It comes from the ## headings in this file, read at build time, which is why it cannot fall out of step with the post. There is no second list to remember.

One consequence worth internalising: a heading promises that a section follows it. Use ## for the sections a reader might jump to, ### for subdivisions inside them, and the panel takes care of itself.

Third-level headings

These render smaller and stay out of the side panel, which makes them right for a point that belongs under a section rather than beside it.

Lists

Unordered, for things with no sequence:

  • Content lives in content/, never in a database
  • Sections come from a registry of typed Blocks
  • Design tokens live in one file

Ordered, for things that genuinely happen in order:

  1. The agent opens a branch
  2. It edits the MDX and commits
  3. CI runs the build, the content lockdown, and the type check
  4. A pull request appears for review

Nested, when a step has steps of its own:

  • Before handing the site over
    • Fill in harness.config.json
    • Run pnpm harness so every platform file regenerates
  • After handing it over
    • Watch the first pull request land before you stop paying attention

Quotes

A blockquote is for somebody else's words. If you want emphasis, bold is the tool.

We used to quote four weeks for a rebrand. With the token file doing the heavy lifting we shipped the last one in three days, and the client couldn't tell.

Code

Inline code marks a filename, a command, or a prop name: content/pages/home.mdx, pnpm dev, variant. Use it for things you would type, not for things you want to look technical.

Fenced blocks take a language and are completely literal. Nothing inside one is parsed as MDX, so a sample can safely contain the same tags a real page uses.

pnpm install
pnpm dev          # http://localhost:3000
pnpm content:check
{
  "slug": "harbour",
  "commands": { "dev": "pnpm dev", "build": "pnpm build" }
}

Tables

Tables are GitHub-flavoured Markdown. A wide one scrolls sideways inside its own box instead of pushing the page out of shape on a phone.

ChangeWho makes itWhere it lands
Homepage copyThe clientA pull request
A new section typeA developersrc/components/
Brand coloursA developersrc/app/globals.css
A blog postEitherA pull request

Images

Images go in by full URL, because the repository deliberately holds no binaries. docs/images.md covers the upload flow. Alt text is not optional.

A dashboard interface with a sidebar and a wide data table

Blocks, inside a post

A post is MDX, so the Blocks that build a landing page work here too. Reach for one when a section needs structure prose cannot carry: figures, a comparison, a run of numbers.

What the starter ships with

19
Blocks in the registry
5
Starter posts, this one included
3
Agent platform runbooks
0kb
Client JS on a plain post

Go easy on this. A post is mostly a voice, and a Block interrupts it.


That is the whole vocabulary. If a post needs something this page does not show, what it probably needs is a new Block, which is four files and a schema rather than a fork of the template.