`write-post` publishes a new blog post the same way this docs site itself is built: a markdown file
with front matter, no database table, no admin UI. The full front-matter reference lives at
[Writing a Blog Post](/docs/content-seo/writing-a-blog-post); this page is the skill's own
step-by-step, including the structural choices that decide whether a post actually gets cited by an
AI assistant.

## What the app already handles

Once the file exists, you don't write any of the following by hand: the canonical URL, Open Graph
and Twitter tags, `BlogPosting` structured data, breadcrumbs, the sitemap entry, both feeds, the
`llms.txt` listing, or the raw-markdown alternate at `/blog/<slug>.md`. All of it reads from the
file you write.

## The steps

1. **Pick the slug and confirm the gap.** The filename is the URL:
   `content/posts/<slug>.md` serves at `/blog/<slug>`. Keep it short, lowercase, hyphenated, and
   stable, since changing it later breaks every inbound link. Don't name a post `tags`; that path is
   reserved for the tag index.
2. **Write the front matter.** `title`, `description`, and `pub_date` are required.
   `description` does real work: it's the meta description, the social card text, the feed summary,
   and the line under the search result, all from one sentence. Add `tags` (each generates a
   `/blog/tags/<tag>` page), and `cta_*` fields only if this post needs a call to action different
   from the site-wide default.
3. **Structure it so it can be quoted.** This is the part that decides whether an assistant cites
   the post. The title is the question, phrased the way someone actually types it. The first
   sentence of the body restates that question. A direct, concrete answer, a number, a list of
   names, a yes with its condition, lands within the first 80 words, written to be lifted out and
   shown with no surrounding context, because that's exactly what happens to it. The long version
   follows under `##` headings that are near-questions themselves, each making sense read alone.
   Prefer specifics to adjectives, and state limits honestly: hedged, qualified claims get cited
   more, not less.
4. **Match the house voice.** Plain, concrete, and agent-neutral. No em dashes anywhere in copy or
   metadata; a spec enforces this, so use a colon, a comma, or a full stop instead. Body headings
   start at `##`, never `#`, since the `#` level renders from the front-matter title.
5. **Link internally, both directions.** Link the new post to two or three existing ones, and add a
   link back to it from the most closely related existing post. An orphan post ranks badly and gets
   crawled late.
6. **Check it renders.** `bin/dev`, then open `/blog/<slug>` and confirm code blocks highlight, the
   CTA is right, and the post shows up in `/sitemap.xml`, `/feed.xml`, and `/llms.txt`.
7. **Verify:** `bin/check` green.

## Notes

Set `draft: true` to keep a work in progress out of the index, the feeds, and the sitemap
entirely. Raw HTML in a post is sanitized on purpose, so a `<script>` tag is dropped; widen the
allowlist in `app/services/markdown_renderer.rb` if a post genuinely needs a tag it currently
strips. Posts are baked into the deploy image, so publishing one means committing and deploying, not
just saving a draft somewhere.

## Next

See how to phrase a request so Claude Code reaches for the right skill on its own:
[Prompting Patterns](/docs/claude-code/prompting-patterns).
