Content and E-E-A-T
Content and E-E-A-T covers 6 of the SEO for Soloschecklist's implementation checks. Authorship, freshness and the signals that separate a page written by someone who did the work from one written to rank.
0 of 6
Saved in this browser
6 checks shown.
Content and E-E-A-T
Authorship, freshness and the signals that separate a page written by someone who did the work from one written to rank.
A real author registry with alias resolution, and no fabricated credentials3/M
Why it matters
Author entities are a genuine trust signal and a fabricated one is a liability. Alias resolution is what stops three spellings of the same name becoming three authors.
How to verify
Confirm every byline on the site resolves to one entry in the author registry, and that every claim in a bio is verifiable.
Every chapter and post shows a last-reviewed date that is actually maintained3/S
Why it matters
A reviewed date is a promise. Bumping it without reviewing is worse than not showing one, because it converts a trust signal into a false claim.
How to verify
Confirm the rendered date comes from frontmatter, and that a documented review process exists with a cadence.
Every non-obvious claim maps to code, official documentation, or an explicit opinion label3/M
Why it matters
This is both an integrity guard and defensible marketing. It is also the only way to keep a large document accurate as the underlying platforms change.
How to verify
Open CLAIMS.md and confirm each entry names a file, a documentation URL, or the label opinion. Spot-check five.
The site publishes what it still gets wrong3/S
Why it matters
A proof page showing 100 percent on everything is less credible than one showing 94 percent and naming the six misses. Honesty about limits is the strongest available trust signal when you have no testimonials.
How to verify
Confirm the public proof page lists current failures, with dates, and that the list is not empty.
Cross-links between chapters, checks and glossary terms run in both directions3/M
Why it matters
One-directional linking creates a hierarchy the crawler descends and never climbs. Bidirectional linking distributes authority across the whole reference rather than pooling it at the top.
How to verify
Pick a chapter, follow its link to a check, and confirm the check links back to that chapter.
No invented testimonials, no fabricated review counts, no aggregateRating without reviews3/S
Why it matters
An aggregateRating with no reviews behind it is a manual action risk and a lie. Real numbers from the work are stronger proof than fake numbers from customers who do not exist.
How to verify
curl -s https://example.com/ | grep -c aggregateRatingThen confirm 0 until real, verifiable reviews exist.
Implementation
kit/seo/schema/commerce.ts/** * Code Kit: commerce schema factories. * Implements checks: schema-product, schema-offer, schema-softwareapplication, * schema-service, pricing-single-source */ import { compact } from "../constants"; import { type JsonLdObject, type NodeRef, nodeId, ref } from "./types"; export type OfferOptions = { url: string; priceCents: number; currency?: string; name?: string; description?: string; availability?: "InStock" | "OutOfStock" | "PreOrder" | "LimitedAvailability"; /** ISO date. Google warns when a price has no validity window. */ priceValidUntil?: string; seller?: NodeRef; /** One-time purchase by default. Set for anything recurring. */ billingDuration?: { value: number; unit: "MON" | "ANN" }; }; function formatPrice(cents: number): string { return (cents / 100).toFixed(2); } export function offer(opts: OfferOptions): JsonLdObject { const price = formatPrice(opts.priceCents); const currency = opts.currency ?? "USD"; return compact({ "@type": "Offer", url: opts.url, name: opts.name, description: opts.description, price, priceCurrency: currency, availability: `https://schema.org/${opts.availability ?? "InStock"}`, priceValidUntil: opts.priceValidUntil, seller: opts.seller, priceSpecification: compact({ "@type": "UnitPriceSpecification", price, priceCurrency: currency, valueAddedTaxIncluded: true, billingDuration: opts.billingDuration?.value, billingIncrement: opts.billingDuration ? 1 : undefined, unitCode: opts.billingDuration?.unit, }), }) as JsonLdObject; } export type AggregateOfferOptions = { url: string; lowPriceCents: number; highPriceCents: number; currency?: string; offerCount: number; offers?: JsonLdObject[]; seller?: NodeRef; }; export function aggregateOffer(opts: AggregateOfferOptions): JsonLdObject { return compact({ "@type": "AggregateOffer", url: opts.url, lowPrice: formatPrice(opts.lowPriceCents), highPrice: formatPrice(opts.highPriceCents), priceCurrency: opts.currency ?? "USD", offerCount: opts.offerCount, availability: "https://schema.org/InStock", seller: opts.seller, offers: opts.offers?.length ? opts.offers : undefined, }) as JsonLdObject; } export type ProductOptions = { siteUrl: string; name: string; description: string; url: string; imageUrl?: string; brandName?: string; category?: string; sku?: string; offers?: JsonLdObject; /** * Ratings are omitted entirely until real, verifiable reviews exist. * Inventing an aggregateRating is a manual action risk and a lie. */ aggregateRating?: { ratingValue: number; reviewCount: number }; id?: string; }; export function product(opts: ProductOptions): JsonLdObject { return compact({ "@type": "Product", "@id": opts.id ?? nodeId(opts.siteUrl, "#product"), name: opts.name, description: opts.description, url: opts.url, image: opts.imageUrl, sku: opts.sku, category: opts.category, brand: opts.brandName ? { "@type": "Brand", name: opts.brandName } : ref(nodeId(opts.siteUrl, "#organization")), offers: opts.offers, aggregateRating: opts.aggregateRating ? { "@type": "AggregateRating", ratingValue: opts.aggregateRating.ratingValue, reviewCount: opts.aggregateRating.reviewCount, } : undefined, }) as JsonLdObject; } export type SoftwareApplicationOptions = { name: string; description: string; url: string; applicationCategory?: string; operatingSystem?: string; /** Pass a free Offer for a free tool; omit for a paid product. */ offers?: JsonLdObject; featureList?: string[]; softwareVersion?: string; id?: string; }; export function softwareApplication(opts: SoftwareApplicationOptions): JsonLdObject { return compact({ "@type": "SoftwareApplication", "@id": opts.id ?? `${opts.url}#software`, name: opts.name, description: opts.description, url: opts.url, applicationCategory: opts.applicationCategory ?? "DeveloperApplication", operatingSystem: opts.operatingSystem ?? "Web browser", softwareVersion: opts.softwareVersion, featureList: opts.featureList?.length ? opts.featureList : undefined, offers: opts.offers, }) as JsonLdObject; } export type ServiceOptions = { name: string; description: string; url: string; serviceType?: string; areaServed?: string; provider?: NodeRef; offers?: JsonLdObject; }; export function service(opts: ServiceOptions): JsonLdObject { return compact({ "@type": "Service", "@id": `${opts.url}#service`, name: opts.name, description: opts.description, url: opts.url, serviceType: opts.serviceType, areaServed: opts.areaServed, provider: opts.provider, offers: opts.offers, }) as JsonLdObject; }