Writing a Blog Post
Front matter reference, the write-post skill, and what the app generates for you automatically.
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 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 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
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.