Modules
Long-form content: the FAQ registry, and where guides went
Guides and comparisons are now built in, as the blog (
docs/blog.md): MDX posts withkind: guideorkind: comparison, held to the rules below bysite/lib/blog. What follows is the older typed-registry pattern the FAQ still uses, kept because it explains the registry the blog's surfaces share.
The template ships one long-form page, /faq, registered in
site/lib/content/index.ts. A source project grew an explainer section and
competitor comparisons on the same pattern and got most of its search traffic
from them. The pattern is what matters; here it is.
One registry, every surface
Every long-form page is an entry in CONTENT (site/lib/content), typed by
ContentPage in types.ts. site/app/sitemap.ts, site/app/llms.txt/route.ts and
site/app/llms-full.txt/route.ts read that array, so a page cannot be in the
sitemap and missing from llms.txt. The one hand-maintained twin is ROUTES
in site/scripts/assert-metadata.sh, deliberately, so the check does not
compute its expectation from the code it checks.
Content is typed data, not Markdown
A guide is an object: title (≤ 60 characters), description (≤ 155, no
price), hand-written published and updated dates, sources, and sections
each carrying a JSX body and a required plain-text mirror. The mirror is
what llms-full.txt and the JSON-LD emit; required because an optional mirror
is a mirror that drifts. MDX was rejected for exactly that: it has no plain-text
twin, and a test cannot walk it for title length and dates the way it walks
objects.
Add to ContentPage for a guide:
export type GuideSection = { id: string; heading: string; body: ReactNode; text: string };
export type Guide = ContentPage & {
kind: "guide";
definition: string; // the one-sentence answer, quoted in llms.txt
sections: readonly GuideSection[];
related: readonly string[]; // paths worth reading next
};
and render it with site/components/ArticleShell.tsx, which already handles the
trail, byline, date and sources.
Comparisons: facts, sources, dates — no verdicts
A comparison page is a table of checkable facts about another product, each
with a source a reader can open and the date it was checked. It is noindex
and out of the sitemap until at least MIN_INDEXABLE_ITEMS (site/lib/seo.ts)
comparisons have been checked, and each one only goes live once its checkedOn
date is set. Competitor names appear only in the comparison data — a test can
walk the source for them (site/lib/test/walk.ts), the inverse of the brand
test.
Dates
updated moves when the prose changes, by hand, in the same commit — never
when a shared component is restyled, and never from a clock. It is printed in
the byline, emitted as dateModified, and reported as the sitemap's
lastModified: one constant, three surfaces.
This page is docs/modules/content.md in the repository, copied 2026-09-25.