Blog and feeds
The blog is a folder of Markdown files, src/content/blog/, that features.blog — on by
default — turns into /blog/ and one page per post. Two RSS feeds come with it, /rss.xml for
listings and /blog/rss.xml for posts, and the sitemap keeps the blog pages in a chunk of their
own. The free edition has all of it.
Write a post
Section titled “Write a post”---title: Why we started this directorydescription: The gap we kept running into, and what this site does about it.pubDate: 2026-09-14tags: [editorial]draft: false---The body is Markdown. Headings, lists, links, images and code blocks render with the site's prose styles.The file name is the URL: my-first-post.md becomes /blog/my-first-post/. Keep posts at the top
level of the folder — a file in a subfolder gets an id with a slash in it, which the URL builder
refuses, and the build stops.
| Field | Required | Default | What it does |
|---|---|---|---|
title |
yes | — | the heading; the <title> through seo.templates.blog.title and titleTemplate |
description |
yes | — | the line under the heading, the meta description and the feed item’s text |
pubDate |
yes | — | the date shown; posts sort newest first |
updatedDate |
no | — | shown as Updated when the day differs; article:modified_time, which the sitemap reads as lastmod |
cover |
no | — | a path or URL rendered at 1200×630 under the header and used as the og:image; not validated |
tags |
no | [] |
chips on the card and the post; free-form, with no pages behind them |
draft |
no | false |
hidden from the build and the feed; shown under pnpm dev with a Draft badge |
Leave out title, description or pubDate and the build stops with
**blog → my-first-post** data does not match collection schema. followed by one line per field.
The index and the post page
Section titled “The index and the post page”/blog/ is one list of every published post, newest first — no pagination and no tag pages. Each
card is the title, the description and the meta line (date, Updated date, tags); the header
carries an RSS feed button, and an empty folder shows No posts yet — check back soon.

A post page is the breadcrumb, the title, the description, the meta line, the cover when there is
one, and the body. It links its feed in the head, marks itself og:type article with published
and modified times, and emits one BlogPosting JSON-LD node. Posts are not in the site search;
only listing pages are indexed.

| Feed | URL | Items | Limit | Discoverable from |
|---|---|---|---|---|
| Listings | /rss.xml |
one per listing: name, tagline, approvedAt, link, category and tag names |
the newest 50 by approvedAt |
<link rel="alternate"> in the head of every page, and the footer’s RSS feed link |
| Blog | /blog/rss.xml |
one per published post: title, description, pubDate, link, tags |
none — every published post, newest first | <link rel="alternate"> on /blog/ and every post, and the RSS feed button on /blog/ |
Both are RSS 2.0. The listings feed is titled with site.name, the blog feed <site name> Blog,
and both carry <language> from site.locale. Item links are absolute against site.url, which
is why the feeds point at the wrong domain until that field is yours. Items carry a description
only — the post body is not in the feed — and there is no Atom, JSON Feed or per-category feed.
The sitemap
Section titled “The sitemap”Blog pages land in sitemap-blog-0.xml, priority 0.5, changefreq monthly, with lastmod read
from each post’s article:modified_time. That chunk is listed in /sitemap-index.xml, which the
head of every page and the Sitemap: line of /robots.txt point at. The two feeds are files, not
pages, so they are not in it; the rest is on
Sitemap and robots.
Replace the demo posts
Section titled “Replace the demo posts”pnpm content:reset --yesIt deletes every .md in src/content/blog/ except unpublished-draft.md, the draft: true
fixture, writes one hello-world.md addressed to you, and clears src/content/listings/ and
public/logos/ at the same time. The log ends with
reset: kept src/content/blog/unpublished-draft.md (starter fixture, draft: true — delete it if you do not want it).
pnpm content:use <dataset> --yes is the reverse: it copies a dataset’s blog/ folder in, again
keeping the fixture.
Verify
Section titled “Verify”| Where | You should see | |
|---|---|---|
| The index | /blog/ |
your posts newest first and the RSS feed button |
| A post | /blog/<file name>/ |
the title, description, date, tags and body |
| The blog feed | /blog/rss.xml |
<title><site name> Blog</title> and one <item> per published post, every link starting with site.url |
| The listings feed | /rss.xml |
<title><site name></title> and at most 50 items |
| The sitemap chunk | /sitemap-blog-0.xml |
/blog/ and one <url> per post with <priority>0.5</priority>; the build log reads sitemap: N urls in M chunks (listings=…, blog=…) |
When it is off or degraded
Section titled “When it is off or degraded”| Config state | The visitor sees | /admin/ reports |
Fix |
|---|---|---|---|
features.blog: false |
no Blog item in the nav; /blog/, the post pages and /blog/rss.xml are gone — the routes answer a body-less 204 at build, so nothing is written, and the deployed paths 404. /rss.xml is unaffected |
nothing | set it back to true |
A post with draft: true |
not on /blog/, not built, not in the feed; under pnpm dev it shows with a Draft badge |
nothing | draft: false, or delete the line |
A post missing title, description or pubDate |
no site: the build stops with **blog → <file>** data does not match collection schema. and names the field |
nothing | add the field |
| A post in a subfolder | no site: the build stops with paths: invalid path segment "<id>" |
nothing | move the file to the top level |
Configuration
Section titled “Configuration”| Field | Default | What it changes |
|---|---|---|
features.blog |
true |
the whole blog: the nav item, /blog/, the post pages, /blog/rss.xml and the sitemap chunk |
seo.templates.blog.title |
'{title}' |
a post’s <title>, before titleTemplate adds the site name |
seo.templates.blog.description |
'{title} — from the {siteName} blog.' |
nothing today: the index and every post pass their own description over it |
site.locale |
'en' |
<language> in both feeds and the date format |
site.url |
required | what makes feed links, canonicals and the sitemap absolute |
Directorysrc/
Directorycontent/blog/ the posts;
unpublished-draft.mdis the draft fixture- …
- content/schema.ts
blogSchema, the frontmatter rules above Directorypages/blog/
/blog/,/blog/<slug>/and/blog/rss.xml- …
- pages/rss.xml.ts the listings feed and its item limit
- templates/BlogIndexPage.astro the index
- templates/BlogPostPage.astro one post
Directorycomponents/blog/ the post card, the meta line and
getPublishedPosts()- …
- integrations/sitemap.ts the chunked sitemap and its build log line