Documentation

Getting found

Search engines, share previews and now language models all read your site before a person does. All three are generated from the spec, so there is nothing to configure and nothing to keep in step.

Why there is no SEO plugin. WordPress has no typed content model, so Yoast and Rank Math must ask you, per post, per post type, “is this a Recipe?” — and map every field by hand. Here the content type already declares its shape once, so every entry of it emits correct markup forever. Zero-config structured data is not a feature that can be retrofitted; it falls out of having a spec.

What every page already emits

Nothing below needs turning on.

<title> and meta descriptionFrom the page, resolved per entry on a collection page.
link rel="canonical"This page's own address — so two URLs for one room are not two pages.
html lang and og:localeYour locale. A screen reader picks its voice from the first; a crawler picks its market.
Open Graphog:title, og:description, og:image, og:image:alt, og:url, og:type, og:site_name.
Twitter/Xtwitter:card — large image when there is one — plus title, description and image.
JSON-LDWhen the content type declares a jsonld mapping. See below.
noindexOn drafts, automatically. A page nobody has published is a page nobody should find.

Titles and descriptions

A page can say its own, and they are templates — so one collection page gives every entry of a type a distinct title without editing them one at a time:

key: property-detail
path: /stay
collection: { from: property }
seo:
title: "{{ entry.name }} in {{ entry.city }}"
description: "{{ entry.summary }}"
image: "asset:hero"
noindex: false

Leave title out and the page's own title is used, with the site's name appended — except on a page already named after the site, because "Riverside Salon — Riverside Salon" is what a template does when nobody looks at the output.

Structured data

A content type maps schema.org properties to its own fields, once:

key: property
label: Property
jsonld:
type: Hotel
properties:
name: name
description: summary
image: photo
priceRange: price

Every entry of that type then emits valid JSON-LD — the markup that produces a rich result rather than a blue link. Article, BlogPosting, Event, Product, Service, LocalBusiness, Person, Organization, Recipe, JobPosting, FAQPage, Review, and for travel Hotel, LodgingBusiness, Place, TouristAttraction, Restaurant. A hotel marked up as a LocalBusiness is a hotel search engines do not know is a hotel.

Addresses, and moving them

A content type's permalink is the URL shape for its entries — /stay/{{ entry.slug }}. Change a page's path and the old one 301s to the new one automatically: the change was recorded with its inverse, so the platform knows where that address used to point without anyone writing a redirect rule.

It only redirects when the page still exists somewhere else. A deleted page has nowhere to send anyone, and pointing it at the homepage tells a search engine that content moved when it did not — worse than an honest 404.

robots.txt, sitemap.xml, llms.txt

Three files nobody writes.

/robots.txtPoints at the sitemap. No Disallow rules you did not ask for.
/sitemap.xmlEvery public page, each with a lastmod from the row behind it — so a crawler can tell what changed without refetching the site.
/llms.txtWhat this site is, for a model rather than a crawler: the summary, the pages, and what each content type holds.

Drafts and noindex pages are in none of them. Set PUBLIC_URL so canonicals and the sitemap carry absolute addresses.

Switching it off

seo:
indexable: true   # false: every page noindex, robots.txt disallows, no sitemap
sitemap: true
llms: true

indexable: false is the switch for a staging copy, a client's site before launch, or an internal tool — one decision at the level it is actually made, rather than noindex on every page and one forgotten on the page that matters. It lives in the spec, so a staging copy made by applying the same files cannot quietly become indexable.

llms.txt is not gated on indexable, because they are different questions: a site kept out of search may still be one an agent has been pointed at deliberately.

Analytics

analytics:
gtag: G-ABC1234567          # Google Analytics 4
plausible: example.com      # the domain it counts under
umami: { id: "…", src: "https://umami.example/script.js" }

There is no field for a pasted <script>, and there will not be. A spec is data an agent may propose and a form may submit; arbitrary JavaScript in it is a cross-site scripting hole with an approval workflow in front of it. Providers are named and the snippet is ours.