Alternatives pages
The alternatives roundups - the index at /alternatives anchored to your own product and a conquest page per rival - their frontmatter, the derived table, the ranked blocks, and the guard that noindexes a thin page.
The alternatives section serves roundups, each ranking several tools against one anchor. index.md is anchored to your own product at /alternatives and is what this page describes; every other file is a conquest page at /alternatives/{rival}, which differs only in its anchor. A roundup answers "alternatives to X"; the {brand} vs {brand} query belongs to the compare engine, and each block links out to that pair page rather than trying to be one.
The demo sites show the boilerplate's own roundups. A generated project ships _template.md and a
worked conquest page, _example-competitor.md, both _-prefixed so neither is ever served. Copy,
rename to {rival}.md, replace every value.
Frontmatter
Fields marked * are required. A missing one is a guard error, never a build failure - see Authoring for the base fields every document shares. The table is not authored here: it derives from the compare fact sheets, and each item names the sheet it is.
title: "Best Acme alternatives, compared" * # NOT the H1. It fills the JSON-LD name and headline, the
# OG card, and the H1 only when the page lists zero tools
description: * ; date: * ; dateModified ; lastVerified: * ; author: * ; primaryKeyword ; translate
competitor: { name: "Acme", url: "/", logo: "..." } * # YOUR product: the baseline the page lists
# alternatives FOR. The headings take the name from
# `config.siteName`, not from here
# NO `matrix:` block - authoring one FAILS the build
methodLine: "Read from each vendor's public pages in 2026. A cross means their docs and site do
not show it, not proof the code lacks it." # markdown rendition only, under the matrix. A
# YEAR, never a month - see the authoring rules
items: * # >= 3, ONE BLOCK PER RIVAL THE TABLE COLUMNS, each
# headed "{n}. Nimbus". You are a table column, never
# an entry here. The order you list them in is not the
# order they render in - they run in `order`
- { name: "Nimbus", product: "nimbus" *, # `product` IS the fact sheet: compare/nimbus.md
url: "https://...", summary: "...", bestFor: "...",
features: ["..."], # <= 5 lines, "Why people pick it". `pros` also accepted
fallsShort: ["..."], # <= 5 lines, SAME neutral label on every tool. `cons` also accepted
verdict: { chooseThem: ["..."], chooseUs: ["..."] }, # optional, markdown rendition only
factStrip: [{ label: "Starting price", value: "$25 per seat" }], # 4 figures, rendition only
bottomLine: "Nimbus is the best Acme alternative for <a specific buyer>.", # <= 28 words
startingPrice ; logo ; screenshot } # ALL THREE DERIVE from the linked sheet. Author one
# only to override it here
betterBuy: # 2-4 admissions, after the blocks and before the FAQ
- { when: "you want the cheapest one-time entry", pick: "Nimbus", # a tool this page compares.
because: "..." } # `when` finishes the card's bold "If", so it starts
# lowercase. `because` comes from the table's cells
verdict: { summary: "..." } * # one or two sentences naming the axis, not a winner
proof ; faq ; features # proof beside the claim it supports; faq is 5-6Publishing your first roundup
Write a fact sheet per tool first, your own included. The table, the prices, the marks and the screenshots all come from them, and a roundup needs three tools plus you.
Copy _template.md to index.md, fill the anchor and one items entry per tool, and point
each one at its sheet with product:. Every sheet except the anchor needs one - the table
columns them all.
Write what the sheets cannot know: each summary and bestFor, the fallsShort lines, the
verdict, the FAQ, each bottomLine, and two to four betterBuy admissions.
Read the build summary. A missing field noindexes the page and names itself; a product naming
no sheet fails the build and names the file to create.
What renders
In order: the byline, the H1, the description and the shortlist - all inside the hero - then a conquest page's whySwitch list, the derived table with its legend, one product block per rival, the better-buy cards, the FAQ, and a CTA card. The byline prints a year, never a month. The visible breadcrumb trail is off here; the BreadcrumbList schema still ships, and the rest of the graph is in the SEO kit. {Brand} below is config.siteName.
| Generated string | Is |
|---|---|
| H1 | Best {Brand} alternatives, compared, counting the blocks it stands over. Falls back to title only when the page lists no tools |
<title> | Best {Brand} Alternatives in {year} (Compared). No count - it went stale whenever a rival was added. An authored seo.title wins |
| Block heading | {n}. {Tool} from items[].name, as an h3. It does not link out; the numeral is aria-hidden |
| Section | Is |
|---|---|
| Shortlist | A ranked card row inside the hero, no heading of its own: rank, mark, name, bestFor, a price chip. Each card jumps to its block |
| The table | Every column shown, the row-label column frozen, a chip per product from three columns up. Its columns and the blocks below it are one list in one order - order on each sheet, yours first on a conquest page |
| Better buy | Two to four cards naming who should not buy yours, and which listed tool suits them instead |
| FAQ | Five or six questions, every answer closed but present in the HTML |
The product block below is the same component the hub and pair pages use.
| Block part | Prints |
|---|---|
| Header row | The mark, the numbered name, the badges and price chip, then bestFor, positioning, the summary paragraph and the tiers chips - with the sheet's 16:9 screenshot top-right at a third of the width, beside them |
| Full width below | Everything from the two lists down, under both columns |
| Two lists | features under Why people pick it, fallsShort under The trade-off vs {Brand}, five lines each at most |
| Three link rows | Derived, never authored: that sheet's verify links, then a chip per published head-to-head the tool has and the roundup about it, then Visit {Tool}. Every vendor link opens in a new tab as nofollow noopener |
| Verdict lines | Nothing. verdict.chooseThem / verdict.chooseUs reach the .md rendition only - say the same fact in features or fallsShort |
| Close | A bold Bottom line: |
The body does not render
The frontmatter IS the page. The body reaches the .md rendition alone, as do every row's
footnote and the methodLine - and never add a line saying you build one of the tools: your
domain, your navbar and your H1 already say so.
The guard
The strict schema runs on every build, next dev rebuilds included.
| Condition | Result |
|---|---|
| A required field is missing or empty | indexable: false: noindex, follow, out of the sitemap and llms.txt, hidden in production. The page still renders minus that block, so one typo never fails pnpm build |
A reserved slug, a duplicate primaryKeyword, a misfiled external or whySwitch | Guard error naming the file, same treatment |
| The table columns a fact sheet no item writes a block for | Guard error naming every missing product. Not a build failure: a sheet is translated into every locale the moment you write it, while the blocks are authored in one, so a locale is legitimately short of blocks until the translation runs |
An authored matrix:, or a product / competitor.slug naming no fact sheet | The build fails, naming the file and the fix - both would lose verified content silently. See comparison tables |
An invalid or duplicated slug in config.content.sections | Config error: the build fails, loudly |
In development a banner lists a guarded page's errors; the build prints one summary line naming the paths.
What an item derives
An item that names a product inherits its entry price, its mark and its screenshot from that fact sheet. Author any of the three here only to override it.
- The guard still asks each item for a
startingPriceor afactStrip, so an item with a derived price carries afactStrip. - The
.mdrendition quotes your starting price frompricingConfig.plans- the first priced non-free plan, inconfig.currency.base. - Locales work as they do on a fact sheet: write English, and the pipeline localizes the prose while pinning slugs, prices and marks. See Locales.
Authoring rules
| Rule | Why |
|---|---|
| One roundup per anchor | Two pages about the same anchor compete for the same query |
Concede in features | A page giving a reader no reason to pick them reads as an advert |
| Correct a fact in its sheet | Never in a sentence here: every surface reads that one file |
Bump lastVerified when you re-check | It dates the badge, and the build flags anything over 180 days old |
The rest of the editorial contract - sourcing a claim, which pages deserve to exist, the shape that gets cited - is in writing comparison pages.
Roundups are ordinary blog posts
A Best {category} tools article is a blog post, not a section of its own,
so it never competes with /alternatives. It writes its own table in its body - the
ComparisonMatrix shortcode - rather than deriving one.
Conquest pages
The same roundup anchored to a rival: the flag, the fields only it carries, and what the guard refuses.
Writing comparison pages
The editorial rules: sourcing a claim, which pages earn their place, and the shape that gets cited.
Comparison tables
Where this page's table comes from: columns, marks, verdict rows and its two build failures.
Authoring
The base frontmatter this page carries, plus shortcodes and authors.
Compare engine
The fact sheets these roundups column, and the head-to-head pages they link out to.
Editorial checklist
What the page needs before you publish it.
SEO kit
The JSON-LD graph and the markdown rendition this page serves.
Authoring content
Base frontmatter for every content document, in-body shortcodes, categories and authors, hand localization, and the optional Content API.
Conquest pages
The roundup anchored to a rival at /alternatives/{rival} - the flag that makes one, the fields only it carries, and what the guard refuses so a near-duplicate never publishes.