SEO for Solos

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.

6
Checks in group
1
Free snippets
3
Average impact
of 5
6
Position
of 12 groups

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.

    content-author-registryImpact 3/5MediumRead the chapter
  • 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.

    content-last-reviewedImpact 3/5SmallRead the chapter
  • 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.

    content-claims-traceableImpact 3/5MediumRead the chapter
  • 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.

    content-known-gaps-publishedImpact 3/5SmallRead the chapter
  • 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.

    content-internal-linkingImpact 3/5MediumRead the 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 aggregateRating

    Then 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;
    }
    content-no-fake-social-proofImpact 3/5SmallFreeRead the chapter