Reference
SEO
The engine auto-generates
sitemap.xml, RSS feeds,robots.txt, and JSON-LD structured data. Open Graph, Twitter, and meta description tags are injected at render time from your frontmatter.
The engine auto-generates JSON-LD schema, OG tags, sitemap.xml, RSS, and canonical URLs from your frontmatter and data/site.yaml. Your job is to set the right data — the engine handles the markup.
Page-level SEO — frontmatter
Every page and post should have these three fields:
---
title: How to Choose a Wedding Photographer | Acme Photography
description: A practical guide to finding the right photographer for your wedding day — questions to ask, what to look for, and how to compare packages.
image: https://images.unsplash.com/photo-1519741497674-611481863552?w=1200&q=80
---
| Field | Notes |
|---|---|
title |
50–60 chars. For the homepage: Brand Name — Tagline. For pages: Page Topic | Brand Name. For posts: the article headline. |
description |
150–160 chars. Summarize what the page is actually about — write for humans, not robots. |
image |
OG image shown when shared on social. Ideally 1200×630px. Falls back to site.defaultImage if omitted. |
Full frontmatter reference
| Field | Type | Required |
|---|---|---|
title |
string |
no |
description |
string |
no |
layout |
string |
no |
image |
string |
no |
date |
string |
no |
publishDate |
string |
no |
perPage |
number |
no |
scripts |
string | string[] |
no |
collection |
string |
no |
item_template |
string |
no |
sort_by |
string |
no |
order |
"asc" | "desc" |
no |
limit |
number |
no |
offset |
number |
no |
where |
object | object[] |
no |
tags |
string | string[] |
no |
type |
string |
no |
robots |
string |
no |
modified |
string |
no |
dynamic |
boolean |
no |
Custom fields anchoring typo detection: author, excerpt, category, featured, draft, slug, cover, summary, subtitle, permalink.
Site-level SEO — data/site.yaml
{
"name": "Acme Photography",
"description": "Austin wedding and portrait photographer",
"defaultImage": "https://yourdomain.com/assets/images/og-default.jpg"
}
defaultImage is used as the OG image fallback for any page that doesn't set its own image: frontmatter.
Auditing pages for missing SEO
When the user asks to improve SEO, check every page in pages/ for:
- Missing
title— every page needs one - Missing
description— every page needs one, especially homepage, service pages, and blog posts - Missing
imageon important pages — homepage, blog posts, landing pages - Generic or duplicate titles — "Home" or "Page" is not a title; each page should have a unique, descriptive title
- Descriptions that are too short or keyword-stuffed — should read naturally
Fix all issues found before moving on.
Controlling indexing with robots
Prevent specific pages from appearing in search results:
---
title: Thank You
robots: noindex
---
Common pages to noindex: /thanks, /success, /404, staging previews, admin pages.
Blog post SEO checklist
Posts benefit most from consistent SEO. For every post ensure:
title— clear, descriptive headline (not clickbait)description— 1–2 sentence summary of what the reader will learndate— required fordatePublishedin Article schemaimage— hero image, shown in social shares and listing cardstags— helps related posts surface; use consistent tag names across posts
Schema.org — what the engine generates automatically
| Data present | Schema generated |
|---|---|
| Any page | WebPage + BreadcrumbList |
Page with date frontmatter (blog post) |
Article with headline, datePublished, image |
site.type + site.address in site.yaml |
LocalBusiness with address and contact |
data/reviews.json exists |
AggregateRating added to LocalBusiness |
site.address.lat + lng |
GeoCoordinates added to LocalBusiness |
For local business schema, use the local-seo skill.
Layout meta tags
templates/layout.html should include these in <head> — check they're present:
<title>{{ site.name }} — {{ page.title }}</title>
<meta name="description" content="{{ page.description }}">
<meta property="og:title" content="{{ page.title }}">
<meta property="og:description" content="{{ page.description }}">
<meta property="og:type" content="{% if page.date %}article{% else %}website{% endif %}">
<meta property="og:url" content="{{ canonicalUrl }}">
{% if page.image %}<meta property="og:image" content="{{ page.image }}">{% endif %}
{% unless page.image %}{% if site.defaultImage %}<meta property="og:image" content="{{ site.defaultImage }}">{% endif %}{% endunless %}
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ page.title }}">
<meta name="twitter:description" content="{{ page.description }}">
<link rel="canonical" href="{{ canonicalUrl }}">
{{ jsonLd }}
If any of these are missing, add them.
Auto-generated infrastructure
The engine generates these automatically — you never create or edit them manually:
| URL | What it is | How to influence it |
|---|---|---|
/sitemap.xml |
XML sitemap of all public pages | Add robots: noindex to exclude a page |
/feed.xml |
RSS feed of blog posts | Posts need date frontmatter to appear |
/robots.txt |
Allows all crawlers, links to sitemap | Not configurable — auto-generated |
Sitemap includes every page that doesn't have robots: noindex. Pages with a future publishDate are excluded until they go live. The date field from post frontmatter becomes <lastmod>.
RSS feed includes all pages/blog/*.md files sorted by date descending. Each entry gets title, description, pubDate, and link from frontmatter. No configuration needed — add posts, they appear in the feed.
To tell Google about the sitemap, submit https://yourdomain.com/sitemap.xml in Google Search Console.
Rules
- Fix real issues — don't just check boxes. A description that's technically present but says "Welcome to our site" is still a problem.
{{ jsonLd }}must be in<head>— not in<body>- Never write schema.org JSON-LD by hand — the engine generates it. Add data to frontmatter and site.yaml instead.
- Never create
sitemap.xml,feed.xml, orrobots.txtas files — the engine serves them dynamically. - Image alt text matters too — every
<img>should have a descriptivealtattribute. The engine addsloading="lazy"automatically but notalt.