Competitor Comparison & Alternatives Pages
Two jobs in one skill:
- Analyze — find the competitive content gap, decide what to build, and prioritize it.
- Build — ship high-converting comparison/alternatives pages on our stack (Next.js 15 App Router + RSC, TypeScript, Tailwind, Payload CMS, next-intl) that win competitive-intent queries with accurate, structured content.
Start with analysis. Building a page nobody can rank for or that nobody clicks is wasted effort.
Part 1 — Competitive Content Gap Analysis
Method (per target query)
- Pull the live SERP for the target query (use your own crawler/SERP tooling). Record the top 10 organic results and every SERP feature present (see SERP table below).
- Classify dominant intent — is the SERP serving comparison pages, listicles, vendor pages, or forum/UGC? If the SERP is all vendor homepages, a comparison page may be the wrong format.
- Teardown the top 3 pages (see Part 2). Capture word count, sub-topics covered, entities mentioned, table presence, schema present, freshness signal.
- Build the gap matrix — sub-topics × competitors. Cells = covered / partial / missing. Your opportunity is the union of (high-value sub-topics) ∩ (thin or missing across the SERP).
- Score and prioritize each opportunity (rubric below) before committing build effort.
Gap matrix (the core artifact)
| Sub-topic / question | Comp A | Comp B | Comp C | Demand | Our angle |
|-----------------------------|:------:|:------:|:------:|:------:|----------------|
| Pricing breakdown w/ totals | part | full | miss | high | total-cost calc|
| Migration steps from X | miss | miss | miss | med | step-by-step |
| API / integrations depth | full | part | full | low | skip / brief |
| Real limits & gotchas | miss | part | miss | high | honest section |
Rule of thumb: prioritize cells that are high demand + missing/partial across all competitors. Those are rankable wedges. A sub-topic everyone covers fully ("table stakes") must be present but is not a differentiator.
Entity & topic coverage check
- Extract the entities (features, integrations, use cases, named competitors) that top pages co-mention. Missing entities = thin coverage Google can detect.
- Don't keyword-stuff. Cover the entity because the topic genuinely needs it, then move on.
Part 2 — Page-Level Competitive Teardown
For each top-3 result, capture a structured snapshot (use your own crawler/tooling; do not assume any specific script exists):
| Dimension |
What to record |
Why it matters |
| Format |
vs / alternatives / roundup / vendor |
Tells you the SERP-expected format |
| Word count & depth |
total words, # of H2s |
Calibrate your minimum, don't pad |
| Table presence |
feature matrix? sortable? |
Tables win featured snippets for comparison intent |
| Schema |
Product, SoftwareApplication, ItemList, FAQPage |
Eligibility for rich results |
| Freshness |
"updated" date, year in title |
Comparison intent is recency-sensitive |
| Bias signal |
balanced vs. hit-piece |
Over-biased pages lose trust + links |
| Internal links |
hub/cluster wiring |
Reveals their topical authority play |
| CTA pattern |
placement, aggressiveness |
Conversion benchmark |
Output: a one-row-per-competitor teardown table feeding the gap matrix. The goal is "be the most complete, most honest, most current page for this query" — derived from evidence, not vibes.
Part 3 — SERP Feature Targeting
Decide which SERP feature you are trying to capture; it changes how you structure the page.
| SERP feature |
How to target |
Structural requirement |
| Featured snippet (table) |
Lead with a clean comparison table near the top |
Real <table>, ≤ ~8 rows, first column = labels |
| Featured snippet (paragraph) |
Answer "is A better than B?" in 40-55 words right after H1 |
Direct, self-contained sentence |
| Featured snippet (list) |
"Best X alternatives" as an ordered/unordered list |
<ol>/<ul> with concise item leads |
| People Also Ask |
Add an FAQ section answering the literal PAA questions |
FAQPage JSON-LD + visible Q/A |
| Product/Review rich result |
Mark up products with ratings you actually have |
Product + AggregateRating (real data only) |
| AI Overview / generative |
Clear entity definitions, comparison facts, cited claims |
Scannable facts, source links |
Never fabricate ratings, review counts, or prices to win a rich result — it's a manual-action and trust risk. Mark up only data you genuinely have.
Part 4 — Prioritization Rubric (0-100)
Score each candidate page before building. Sum the weighted dimensions:
| Dimension |
Weight |
0 |
50 |
100 |
| Demand (search volume / trend) |
25 |
negligible |
steady mid |
high & growing |
| Win probability (gap size vs. our authority) |
25 |
SERP fully saturated |
beatable on depth |
clear wedge, weak incumbents |
| Commercial intent (proximity to revenue) |
25 |
informational only |
mid-funnel |
high buyer intent (vs/pricing) |
| Effort inverse (lower effort = higher score) |
15 |
needs deep original research |
moderate |
data we already have |
| Strategic fit (cluster/hub support) |
10 |
orphan |
adjacent |
reinforces a money cluster |
Decision bands: ≥ 70 build now · 50-69 backlog with a date · < 50 skip or revisit when authority grows.
Falsifiability — how would we know this failed?
Define the failure condition before publishing, so the page is accountable:
- Hypothesis: "This vs page will rank top-5 for
[A vs B] and convert at ≥ X%."
- Leading indicator (2-4 weeks): impressions in Search Console for the target query rising; average position breaking into top 20. If impressions are flat after 4 weeks of being indexed, the page is mis-targeted (wrong intent/format) — diagnose before adding more pages like it.
- Lagging indicator (8-12 weeks): top-5 position + assisted conversions. No movement here despite healthy leading indicators ⇒ on-page relevance/depth gap or CTR problem (fix title/snippet), not a "wait longer" problem.
- Kill criterion: no top-20 position and < N impressions after 12 weeks ⇒ consolidate into a stronger page or unpublish; don't let thin comparison pages dilute the cluster.
Page Types
1. "X vs Y" Comparison
Direct head-to-head, balanced feature-by-feature analysis, clear justified verdict. Target: [A] vs [B].
2. "Alternatives to X"
List of alternatives, each with summary, pros/cons, best-for use case. Target: [Product] alternatives, best alternatives to [Product].
3. "Best [Category] Tools" Roundup
Curated ranking with stated criteria. Target: best [category] tools [year], top [category] software.
4. Comparison Table / Matrix
Multiple products in columns, sortable/filterable if interactive. Target: [category] comparison, [category] comparison chart.
Keyword Targeting
| Pattern |
Example |
Intent strength |
[A] vs [B] |
"Slack vs Teams" |
high, mid-funnel |
[A] alternatives |
"Figma alternatives" |
high, commercial |
[A] alternatives [year] |
"Notion alternatives 2026" |
high, recency |
best [category] tools |
"best project management tools" |
high, top-funnel |
[A] vs [B] for [use case] |
"AWS vs Azure for startups" |
medium, qualified |
[A] vs [B] pricing |
"HubSpot vs Salesforce pricing" |
medium, high buyer intent |
is [A] better than [B] |
"is Notion better than Confluence" |
medium, snippet-prone |
Title / H1 formulas
- vs:
[A] vs [B]: [Key Differentiator] ([Year])
- alternatives:
[N] Best [A] Alternatives in [Year] (Free & Paid)
- roundup:
[N] Best [Category] Tools in [Year], Compared & Ranked
- H1: match title intent, primary keyword natural, < 70 chars.
Build — Next.js 15 (App Router + RSC)
Comparison page route + metadata
// app/[locale]/compare/[slug]/page.tsx
import type { Metadata } from "next";
import { getTranslations } from "next-intl/server";
import { getComparison } from "@/lib/payload/comparisons"; // your Payload fetch
import { ComparisonTable } from "@/components/comparison-table";
import { JsonLd } from "@/components/json-ld";
export async function generateMetadata(
{ params }: { params: Promise<{ locale: string; slug: string }> },
): Promise<Metadata> {
const { slug, locale } = await params;
const c = await getComparison(slug, locale);
if (!c) return {};
const title = `${c.productA} vs ${c.productB}: ${c.differentiator} (${new Date().getFullYear()})`;
return {
title,
description: c.metaDescription,
alternates: { canonical: `/compare/${slug}` },
openGraph: { title, description: c.metaDescription, type: "article" },
};
}
export default async function ComparePage(
{ params }: { params: Promise<{ locale: string; slug: string }> },
) {
const { slug, locale } = await params;
const c = await getComparison(slug, locale); // RSC: fetched on the server
if (!c) return null;
const t = await getTranslations("compare");
return (
<article className="mx-auto max-w-4xl px-4 py-10">
<h1 className="text-3xl font-bold tracking-tight">
{c.productA} vs {c.productB}
</h1>
{/* 40-55 word self-contained verdict → targets paragraph featured snippet */}
<p className="mt-4 text-lg text-muted-foreground">{c.verdict}</p>
<ComparisonTable rows={c.rows} columns={[c.productA, c.productB]} />
<JsonLd data={buildComparisonSchema(c)} />
<time className="mt-8 block text-sm text-muted-foreground" dateTime={c.updatedAt}>
{t("updated", { date: new Date(c.updatedAt).toLocaleDateString(locale) })}
</time>
</article>
);
}
Accessible, snippet-friendly table component
// components/comparison-table.tsx
type Cell = "yes" | "no" | "partial" | string;
export function ComparisonTable(
{ rows, columns }: { rows: { feature: string; values: Cell[] }[]; columns: string[] },
) {
const mark = (v: Cell) =>
v === "yes" ? <span aria-label="Yes">✓</span>
: v === "no" ? <span aria-label="No">—</span>
: v === "partial" ? <span aria-label="Partial">◐</span>
: v;
return (
<table className="mt-8 w-full border-collapse text-sm">
<caption className="sr-only">Feature comparison</caption>
<thead>
<tr>
<th scope="col" className="text-left p-3">Feature</th>
{columns.map((c) => (
<th key={c} scope="col" className="p-3 text-center">{c}</th>
))}
</tr>
</thead>
<tbody>
{rows.map((r) => (
<tr key={r.feature} className="border-t">
<th scope="row" className="p-3 text-left font-medium">{r.feature}</th>
{r.values.map((v, i) => <td key={i} className="p-3 text-center">{mark(v)}</td>)}
</tr>
))}
</tbody>
</table>
);
}
Data accuracy (enforce in content, not just markup)
- Every feature/pricing claim verifiable from a public source; store the source URL alongside the value in Payload.
- Pricing carries an "as of [date]" note; review quarterly or when a competitor ships a major change.
- Prefer Payload-managed comparison data over hardcoded JSX so non-devs can keep it current and
revalidateTag refreshes the page.
Schema Markup (JSON-LD)
Render via a small <JsonLd> server component (<script type="application/ld+json">). Use only the type(s) the page genuinely supports.
// SoftwareApplication (software comparisons)
{ "@context": "https://schema.org", "@type": "SoftwareApplication",
"name": "[Software]", "applicationCategory": "[Category]", "operatingSystem": "[OS]",
"offers": { "@type": "Offer", "price": "[Price]", "priceCurrency": "EUR" } }
// ItemList (roundup pages)
{ "@context": "https://schema.org", "@type": "ItemList",
"itemListOrder": "https://schema.org/ItemListOrderDescending",
"itemListElement": [
{ "@type": "ListItem", "position": 1, "name": "[Product]", "url": "[URL]" }
] }
// FAQPage (target People Also Ask) — mirror visible Q/A on the page
{ "@context": "https://schema.org", "@type": "FAQPage",
"mainEntity": [
{ "@type": "Question", "name": "Is [A] better than [B]?",
"acceptedAnswer": { "@type": "Answer", "text": "..." } }
] }
Product + AggregateRating only when you hold real ratings (don't invent reviewCount/ratingValue).
- Keep JSON-LD in sync with visible content — mismatches risk a structured-data penalty.
Conversion & Trust
- CTA placement: brief summary + CTA above fold; secondary CTA after the table; final recommendation + CTA at the bottom. Avoid aggressive CTAs inside competitor-description sections — it reads as biased and kills trust.
- Social proof: testimonials tied to the comparison criteria, third-party ratings with source links, "switched from [Competitor]" stories.
- Pricing: highlight value (not just lowest price), surface hidden costs (setup, per-seat, overage), link to the full pricing page.
- Trust signals: visible "last updated" date, author with relevant expertise, methodology disclosure, explicit disclosure of which product is ours.
Fairness (non-negotiable)
- Accuracy: all competitor info verifiable from public sources.
- No defamation / no false or misleading claims.
- Cite sources; acknowledge competitor strengths honestly.
- Balanced presentation beats a hit-piece — it earns links and survives competitor scrutiny.
Internal Linking
- Link from comparison sections to your own product/feature pages.
- Cross-link related comparisons ("A vs B" ↔ "A vs C") and wire them into the category hub.
- Breadcrumb: Home > Comparisons > [This Page].
- "Related comparisons" block at the bottom; link to any cited case studies/testimonials.
Deliverables
- Gap matrix (sub-topic × competitor) + chosen wedges.
- Teardown table of top-3 SERP pages.
- Prioritization scores (0-100) with build/backlog/skip decision and the falsifiability hypothesis per page.
- Page implementation — RSC route + metadata + accessible table (≥ 1,500 words only if depth genuinely warrants it; depth over padding).
- JSON-LD matching visible content.
- Recommendations — improvements to existing comparison pages, new opportunities, schema/CTA fixes.
Error Handling
| Scenario |
Action |
| Competitor URL unreachable |
Report which URLs failed; proceed with available data and note the gap. |
| Insufficient competitor data |
Use "Not publicly available" in tables rather than guessing; flag the missing cells. |
| No product overlap |
Report different markets; suggest overlapping competitors or pivot to a category roundup. |
| SERP dominated by non-comparison formats |
Don't force a vs page — match the format the SERP rewards (listicle/vendor/forum). |
Parts adapted from claude-seo (MIT, © 2026 agricidaniel).
1---2name: seo-competitor-pages3description: Build and audit competitor comparison, "alternatives to X", and category roundup pages — competitive content gap analysis, SERP-feature targeting, page-level teardown, feature matrices, JSON-LD, and a 0-100 prioritization rubric. Use when user says "comparison page", "vs page", "alternatives page", "competitor comparison", "content gap", "SERP analysis", "X vs Y". Triggers on: versus, compare competitors, alternative to, comparison table, gap analysis, SERP features, competitive teardown.4---56# Competitor Comparison & Alternatives Pages78Two jobs in one skill:91. **Analyze** — find the competitive content gap, decide what to build, and prioritize it.102. **Build** — ship high-converting comparison/alternatives pages on our stack (Next.js 15 App Router + RSC, TypeScript, Tailwind, Payload CMS, next-intl) that win competitive-intent queries with accurate, structured content.1112Start with analysis. Building a page nobody can rank for or that nobody clicks is wasted effort.1314---1516## Part 1 — Competitive Content Gap Analysis1718### Method (per target query)191. **Pull the live SERP** for the target query (use your own crawler/SERP tooling). Record the top 10 organic results and every SERP feature present (see SERP table below).202. **Classify dominant intent** — is the SERP serving comparison pages, listicles, vendor pages, or forum/UGC? If the SERP is all vendor homepages, a comparison page may be the wrong format.213. **Teardown the top 3 pages** (see Part 2). Capture word count, sub-topics covered, entities mentioned, table presence, schema present, freshness signal.224. **Build the gap matrix** — sub-topics × competitors. Cells = covered / partial / missing. Your opportunity is the union of (high-value sub-topics) ∩ (thin or missing across the SERP).235. **Score and prioritize** each opportunity (rubric below) before committing build effort.2425### Gap matrix (the core artifact)26```27| Sub-topic / question | Comp A | Comp B | Comp C | Demand | Our angle |28|-----------------------------|:------:|:------:|:------:|:------:|----------------|29| Pricing breakdown w/ totals | part | full | miss | high | total-cost calc|30| Migration steps from X | miss | miss | miss | med | step-by-step |31| API / integrations depth | full | part | full | low | skip / brief |32| Real limits & gotchas | miss | part | miss | high | honest section |33```34Rule of thumb: prioritize cells that are **high demand + missing/partial across all competitors**. Those are rankable wedges. A sub-topic everyone covers fully ("table stakes") must be present but is not a differentiator.3536### Entity & topic coverage check37- Extract the entities (features, integrations, use cases, named competitors) that top pages co-mention. Missing entities = thin coverage Google can detect.38- Don't keyword-stuff. Cover the entity *because the topic genuinely needs it*, then move on.3940---4142## Part 2 — Page-Level Competitive Teardown4344For each top-3 result, capture a structured snapshot (use your own crawler/tooling; do not assume any specific script exists):4546| Dimension | What to record | Why it matters |47|-----------|----------------|----------------|48| Format | vs / alternatives / roundup / vendor | Tells you the SERP-expected format |49| Word count & depth | total words, # of H2s | Calibrate your minimum, don't pad |50| Table presence | feature matrix? sortable? | Tables win featured snippets for comparison intent |51| Schema | Product, SoftwareApplication, ItemList, FAQPage | Eligibility for rich results |52| Freshness | "updated" date, year in title | Comparison intent is recency-sensitive |53| Bias signal | balanced vs. hit-piece | Over-biased pages lose trust + links |54| Internal links | hub/cluster wiring | Reveals their topical authority play |55| CTA pattern | placement, aggressiveness | Conversion benchmark |5657Output: a one-row-per-competitor teardown table feeding the gap matrix. The goal is "be the most complete, most honest, most current page for this query" — derived from evidence, not vibes.5859---6061## Part 3 — SERP Feature Targeting6263Decide which SERP feature you are trying to capture; it changes how you structure the page.6465| SERP feature | How to target | Structural requirement |66|--------------|---------------|------------------------|67| Featured snippet (table) | Lead with a clean comparison table near the top | Real `<table>`, ≤ ~8 rows, first column = labels |68| Featured snippet (paragraph) | Answer "is A better than B?" in 40-55 words right after H1 | Direct, self-contained sentence |69| Featured snippet (list) | "Best X alternatives" as an ordered/unordered list | `<ol>`/`<ul>` with concise item leads |70| People Also Ask | Add an FAQ section answering the literal PAA questions | `FAQPage` JSON-LD + visible Q/A |71| Product/Review rich result | Mark up products with ratings you actually have | `Product` + `AggregateRating` (real data only) |72| AI Overview / generative | Clear entity definitions, comparison facts, cited claims | Scannable facts, source links |7374Never fabricate ratings, review counts, or prices to win a rich result — it's a manual-action and trust risk. Mark up only data you genuinely have.7576---7778## Part 4 — Prioritization Rubric (0-100)7980Score each candidate page before building. Sum the weighted dimensions:8182| Dimension | Weight | 0 | 50 | 100 |83|-----------|:------:|---|----|-----|84| Demand (search volume / trend) | 25 | negligible | steady mid | high & growing |85| Win probability (gap size vs. our authority) | 25 | SERP fully saturated | beatable on depth | clear wedge, weak incumbents |86| Commercial intent (proximity to revenue) | 25 | informational only | mid-funnel | high buyer intent (vs/pricing) |87| Effort inverse (lower effort = higher score) | 15 | needs deep original research | moderate | data we already have |88| Strategic fit (cluster/hub support) | 10 | orphan | adjacent | reinforces a money cluster |8990**Decision bands:** ≥ 70 build now · 50-69 backlog with a date · < 50 skip or revisit when authority grows.9192### Falsifiability — how would we know this failed?93Define the failure condition *before* publishing, so the page is accountable:94- **Hypothesis:** "This vs page will rank top-5 for `[A vs B]` and convert at ≥ X%."95- **Leading indicator (2-4 weeks):** impressions in Search Console for the target query rising; average position breaking into top 20. If impressions are flat after 4 weeks of being indexed, the page is mis-targeted (wrong intent/format) — diagnose before adding more pages like it.96- **Lagging indicator (8-12 weeks):** top-5 position + assisted conversions. No movement here despite healthy leading indicators ⇒ on-page relevance/depth gap or CTR problem (fix title/snippet), not a "wait longer" problem.97- **Kill criterion:** no top-20 position and < N impressions after 12 weeks ⇒ consolidate into a stronger page or unpublish; don't let thin comparison pages dilute the cluster.9899---100101## Page Types102103### 1. "X vs Y" Comparison104Direct head-to-head, balanced feature-by-feature analysis, clear justified verdict. Target: `[A] vs [B]`.105106### 2. "Alternatives to X"107List of alternatives, each with summary, pros/cons, best-for use case. Target: `[Product] alternatives`, `best alternatives to [Product]`.108109### 3. "Best [Category] Tools" Roundup110Curated ranking with stated criteria. Target: `best [category] tools [year]`, `top [category] software`.111112### 4. Comparison Table / Matrix113Multiple products in columns, sortable/filterable if interactive. Target: `[category] comparison`, `[category] comparison chart`.114115---116117## Keyword Targeting118119| Pattern | Example | Intent strength |120|---------|---------|-----------------|121| `[A] vs [B]` | "Slack vs Teams" | high, mid-funnel |122| `[A] alternatives` | "Figma alternatives" | high, commercial |123| `[A] alternatives [year]` | "Notion alternatives 2026" | high, recency |124| `best [category] tools` | "best project management tools" | high, top-funnel |125| `[A] vs [B] for [use case]` | "AWS vs Azure for startups" | medium, qualified |126| `[A] vs [B] pricing` | "HubSpot vs Salesforce pricing" | medium, high buyer intent |127| `is [A] better than [B]` | "is Notion better than Confluence" | medium, snippet-prone |128129### Title / H1 formulas130- vs: `[A] vs [B]: [Key Differentiator] ([Year])`131- alternatives: `[N] Best [A] Alternatives in [Year] (Free & Paid)`132- roundup: `[N] Best [Category] Tools in [Year], Compared & Ranked`133- H1: match title intent, primary keyword natural, < 70 chars.134135---136137## Build — Next.js 15 (App Router + RSC)138139### Comparison page route + metadata140```tsx141// app/[locale]/compare/[slug]/page.tsx142import type { Metadata } from "next";143import { getTranslations } from "next-intl/server";144import { getComparison } from "@/lib/payload/comparisons"; // your Payload fetch145import { ComparisonTable } from "@/components/comparison-table";146import { JsonLd } from "@/components/json-ld";147148export async function generateMetadata(149 { params }: { params: Promise<{ locale: string; slug: string }> },150): Promise<Metadata> {151 const { slug, locale } = await params;152 const c = await getComparison(slug, locale);153 if (!c) return {};154 const title = `${c.productA} vs ${c.productB}: ${c.differentiator} (${new Date().getFullYear()})`;155 return {156 title,157 description: c.metaDescription,158 alternates: { canonical: `/compare/${slug}` },159 openGraph: { title, description: c.metaDescription, type: "article" },160 };161}162163export default async function ComparePage(164 { params }: { params: Promise<{ locale: string; slug: string }> },165) {166 const { slug, locale } = await params;167 const c = await getComparison(slug, locale); // RSC: fetched on the server168 if (!c) return null;169 const t = await getTranslations("compare");170171 return (172 <article className="mx-auto max-w-4xl px-4 py-10">173 <h1 className="text-3xl font-bold tracking-tight">174 {c.productA} vs {c.productB}175 </h1>176 {/* 40-55 word self-contained verdict → targets paragraph featured snippet */}177 <p className="mt-4 text-lg text-muted-foreground">{c.verdict}</p>178179 <ComparisonTable rows={c.rows} columns={[c.productA, c.productB]} />180181 <JsonLd data={buildComparisonSchema(c)} />182 <time className="mt-8 block text-sm text-muted-foreground" dateTime={c.updatedAt}>183 {t("updated", { date: new Date(c.updatedAt).toLocaleDateString(locale) })}184 </time>185 </article>186 );187}188```189190### Accessible, snippet-friendly table component191```tsx192// components/comparison-table.tsx193type Cell = "yes" | "no" | "partial" | string;194export function ComparisonTable(195 { rows, columns }: { rows: { feature: string; values: Cell[] }[]; columns: string[] },196) {197 const mark = (v: Cell) =>198 v === "yes" ? <span aria-label="Yes">✓</span>199 : v === "no" ? <span aria-label="No">—</span>200 : v === "partial" ? <span aria-label="Partial">◐</span>201 : v;202 return (203 <table className="mt-8 w-full border-collapse text-sm">204 <caption className="sr-only">Feature comparison</caption>205 <thead>206 <tr>207 <th scope="col" className="text-left p-3">Feature</th>208 {columns.map((c) => (209 <th key={c} scope="col" className="p-3 text-center">{c}</th>210 ))}211 </tr>212 </thead>213 <tbody>214 {rows.map((r) => (215 <tr key={r.feature} className="border-t">216 <th scope="row" className="p-3 text-left font-medium">{r.feature}</th>217 {r.values.map((v, i) => <td key={i} className="p-3 text-center">{mark(v)}</td>)}218 </tr>219 ))}220 </tbody>221 </table>222 );223}224```225226### Data accuracy (enforce in content, not just markup)227- Every feature/pricing claim verifiable from a public source; store the source URL alongside the value in Payload.228- Pricing carries an "as of [date]" note; review quarterly or when a competitor ships a major change.229- Prefer Payload-managed comparison data over hardcoded JSX so non-devs can keep it current and `revalidateTag` refreshes the page.230231---232233## Schema Markup (JSON-LD)234235Render via a small `<JsonLd>` server component (`<script type="application/ld+json">`). Use only the type(s) the page genuinely supports.236237```json238// SoftwareApplication (software comparisons)239{ "@context": "https://schema.org", "@type": "SoftwareApplication",240 "name": "[Software]", "applicationCategory": "[Category]", "operatingSystem": "[OS]",241 "offers": { "@type": "Offer", "price": "[Price]", "priceCurrency": "EUR" } }242```243```json244// ItemList (roundup pages)245{ "@context": "https://schema.org", "@type": "ItemList",246 "itemListOrder": "https://schema.org/ItemListOrderDescending",247 "itemListElement": [248 { "@type": "ListItem", "position": 1, "name": "[Product]", "url": "[URL]" }249 ] }250```251```json252// FAQPage (target People Also Ask) — mirror visible Q/A on the page253{ "@context": "https://schema.org", "@type": "FAQPage",254 "mainEntity": [255 { "@type": "Question", "name": "Is [A] better than [B]?",256 "acceptedAnswer": { "@type": "Answer", "text": "..." } }257 ] }258```259- `Product` + `AggregateRating` only when you hold real ratings (don't invent `reviewCount`/`ratingValue`).260- Keep JSON-LD in sync with visible content — mismatches risk a structured-data penalty.261262---263264## Conversion & Trust265266- **CTA placement:** brief summary + CTA above fold; secondary CTA after the table; final recommendation + CTA at the bottom. Avoid aggressive CTAs inside competitor-description sections — it reads as biased and kills trust.267- **Social proof:** testimonials tied to the comparison criteria, third-party ratings *with source links*, "switched from [Competitor]" stories.268- **Pricing:** highlight value (not just lowest price), surface hidden costs (setup, per-seat, overage), link to the full pricing page.269- **Trust signals:** visible "last updated" date, author with relevant expertise, methodology disclosure, explicit disclosure of which product is ours.270271### Fairness (non-negotiable)272- Accuracy: all competitor info verifiable from public sources.273- No defamation / no false or misleading claims.274- Cite sources; acknowledge competitor strengths honestly.275- Balanced presentation beats a hit-piece — it earns links and survives competitor scrutiny.276277---278279## Internal Linking280- Link from comparison sections to your own product/feature pages.281- Cross-link related comparisons ("A vs B" ↔ "A vs C") and wire them into the category hub.282- Breadcrumb: Home > Comparisons > [This Page].283- "Related comparisons" block at the bottom; link to any cited case studies/testimonials.284285---286287## Deliverables288- **Gap matrix** (sub-topic × competitor) + chosen wedges.289- **Teardown table** of top-3 SERP pages.290- **Prioritization scores** (0-100) with build/backlog/skip decision and the falsifiability hypothesis per page.291- **Page implementation** — RSC route + metadata + accessible table (≥ 1,500 words only if depth genuinely warrants it; depth over padding).292- **JSON-LD** matching visible content.293- **Recommendations** — improvements to existing comparison pages, new opportunities, schema/CTA fixes.294295## Error Handling296| Scenario | Action |297|----------|--------|298| Competitor URL unreachable | Report which URLs failed; proceed with available data and note the gap. |299| Insufficient competitor data | Use "Not publicly available" in tables rather than guessing; flag the missing cells. |300| No product overlap | Report different markets; suggest overlapping competitors or pivot to a category roundup. |301| SERP dominated by non-comparison formats | Don't force a vs page — match the format the SERP rewards (listicle/vendor/forum). |302303> Parts adapted from [claude-seo](https://github.com/AgriciDaniel/claude-seo) (MIT, © 2026 agricidaniel).