A post is a markdown file in `content/posts/`. Drop the file, commit, deploy. There's no database
table, no migration, and no admin UI to log into: your writing is reviewed, versioned, and shipped
exactly like your code, in whatever editor you already use.

```
content/posts/charging-for-your-app.md   ->   /blog/charging-for-your-app
```

The fastest way to write one is the **`write-post`** skill in Claude Code; see
[The write-post Skill](/docs/claude-code/skill-write-post) for its full walkthrough. This page is
the front-matter reference it, and you, draw from.

## Front matter fields

`title`, `description`, and `pub_date` are required. Everything else is optional.

| Field | What it does |
|---|---|
| `title` | The `<h1>`, the `<title>`, the feed entry, the JSON-LD headline |
| `description` | Meta description, social card text, feed summary, search snippet |
| `pub_date` | Sort order and `datePublished`. Posts list newest first |
| `updated_date` | Shown on the post, published as `dateModified` (a freshness signal) |
| `author` | Defaults to `founder_name` in `config/initializers/app_identity.rb` |
| `tags` | Tag chips, the `/blog/tags/<tag>` pages, JSON-LD keywords |
| `draft` | `true` keeps it out of the index, the feeds, and the sitemap |
| `image` | Per-post social share image. Falls back to `/og.png` |
| `cta_title`, `cta_body`, `cta_label`, `cta_url` | The bottom-of-post call to action |

`content/posts/hello-world.md` is a worked example carrying every field.

### Why the CTA lives in front matter, not in a view

So the shipped partial carries no marketing copy of its own. Each post supplies its own
call-to-action; when it doesn't, the fallback builds itself from your `app_name` and
`app_description`. That means the blog works correctly the moment you rebrand with `bootstrap-app`,
with nothing left over to find and delete.

## What you get without doing anything

Every post automatically gets a canonical URL built from its path, full Open Graph and Twitter
tags, `BlogPosting` and `BreadcrumbList` structured data, an entry in `/sitemap.xml` with a
`lastmod`, an entry in both `/feed.xml` and `/feed.json`, a line in `/llms.txt` with its full text
in `/llms-full.txt`, a raw markdown alternate at `/blog/<slug>.md`, and server-rendered syntax
highlighting with zero JavaScript on the page. None of this is something you add by hand. See
[SEO and Structured Data](/docs/content-seo/seo-and-structured-data) for how it's built.

## Style

Plain and concrete, matching the voice of the site's own landing page. No em dashes anywhere in
copy or metadata; a spec enforces this across both the blog and this docs site, so use a colon, a
comma, or a full stop instead. Start body headings at `##`, never `#`, since `#` renders from the
front-matter title.

## Checking your work

```bash
bin/dev
open http://localhost:3000/blog
```

Then confirm the machine-readable surfaces: `/sitemap.xml`, `/feed.xml`, `/feed.json`, `/llms.txt`,
and `/blog/<slug>.md`.

## Next

See exactly what SEO machinery is generated for you, and why:
[SEO and Structured Data](/docs/content-seo/seo-and-structured-data).
