SEO for Solos
Chapter 03Free

Structured data

JSON-LD that describes a page to a machine. What the 42 types are for, which ones earned their place, and why optional fields must be omitted rather than emitted empty.

5 min read · updated 2026-08-13· last reviewed 2026-07-26

What structured data is actually for

The principle

Structured data does not make a page rank. It makes a page legible: it turns prose a parser has to guess at into assertions a parser can read. Two things follow from that. First, the return is largest where the page's meaning is hardest to infer from the text, which is why it matters more on a product page than on an essay. Second, an assertion that is wrong is worse than no assertion, because you have now told a machine something false in a form it trusts.

The practical consequence for a large site is that structured data is a data modelling exercise, not a markup exercise. If you find yourself writing JSON-LD by hand per page, the model is wrong.

The implementation

Every schema type in the reference implementation is a function that takes typed options and returns a plain object, and every one of them runs its output through a compactor:

typescript
export function compact<T>(value: T): T {
  if (Array.isArray(value)) {
    const cleaned = value.map(compact).filter((item) => item !== undefined && item !== null && item !== "");
    return cleaned as unknown as T;
  }
  if (value && typeof value === "object" && !(value instanceof Date)) {
    const entries = Object.entries(value as Record<string, unknown>)
      .map(([key, val]) => [key, compact(val)] as const)
      .filter(([, val]) => {
        if (val === undefined || val === null || val === "") return false;
        if (Array.isArray(val) && val.length === 0) return false;
        if (typeof val === "object" && Object.keys(val as object).length === 0) return false;
        return true;
      });
    return Object.fromEntries(entries) as T;
  }
  return value;
}

The types that earned their place

The principle

Forty-two types sounds like a lot until you count what a mixed content and commerce site actually contains: an organisation, a website, a product with offers, articles, authors, breadcrumbs on every page, FAQs, procedural content, a glossary, tool pages, service pages, and list pages for each hub. Each of those is a distinct type, and most pages carry three or four of them at once.

Not all of them do equal work. Being honest about which ones did nothing is more useful than a list of all the ones you could add.

What produced a visible result

  • BreadcrumbList. Replaces the URL in the result with a readable path. Visible, immediate, and cheap.
  • Product with Offer. Price and availability shown in the result. Only relevant if you sell something, and mandatory if you do.
  • FAQPage. Narrower than it was, and now more valuable for answer extraction than for rich results.
  • Organization. Not a rich result. It is the anchor the whole entity graph hangs from, which matters more.
  • Article with dateModified. The freshness signal that a crawler can read without diffing the page.

What produced nothing measurable

  • SoftwareApplication. Correct to include on a tool page, no observed effect on presentation.
  • Service. Correct, invisible.
  • DefinedTerm and DefinedTermSet. No rich result. Kept anyway, because it is the correct description of a glossary and because answer engines quote definitions.
  • SpeakableSpecification. No measurable effect. Kept because writing it forces you to have a short self-contained answer on the page, and that discipline is worth more than the markup.

FAQ schema, and the line you must not cross

The principle

Every question and answer marked up as a FAQ has to be visible on the page. Marking up questions that are not rendered is a documented manual action trigger, and the sites that get caught are almost never trying to cheat: they built an accordion that mounts its content on click, so the crawler sees an empty container.

The fix is to use a native disclosure element, which puts the content in the HTML whether the disclosure is open or closed.

The implementation

tsx
<details className="group">
  <summary>{faq.question}</summary>
  <div>{faq.answer}</div>
</details>
typescript
export function faqPage(opts: { url: string; faqs: FaqEntry[] }): JsonLdObject | undefined {
  if (opts.faqs.length === 0) return undefined;
  return compact({
    "@type": "FAQPage",
    "@id": `${opts.url}#faq`,
    mainEntity: opts.faqs.map((faq) => ({
      "@type": "Question",
      name: faq.question,
      acceptedAnswer: { "@type": "Answer", text: faq.answer },
    })),
  });
}

Note the early return. A FAQPage node with an empty mainEntity is a validation error, and it is what you get on any page where the FAQ list happens to be empty.

The principle

A breadcrumb in structured data with no matching visible navigation is a mismatch between what the markup claims and what the page contains. It is also a wasted internal link: the visible breadcrumb is a real anchor a crawler follows, and on a deep programmatic tier it is often the only upward link on the page.

Render both from the same array, so they cannot disagree.

The implementation

typescript
export function breadcrumbList(opts: { url: string; items: BreadcrumbItem[] }) {
  if (opts.items.length === 0) return undefined;
  const lastIndex = opts.items.length - 1;
  return {
    "@type": "BreadcrumbList",
    "@id": `${opts.url}#breadcrumb`,
    itemListElement: opts.items.map((item, index) =>
      compact({
        "@type": "ListItem",
        position: index + 1,
        name: item.name,
        item: index === lastIndex ? undefined : item.url,
      }),
    ),
  };
}

The last item carries no item URL. That is Google's documented shape: the final crumb is the current page, and giving it a URL produces a warning.

Checks this chapter covers

Each one has a command you can run against your own site.

Product analytics are optional. On the skill page, X advertising measurement is also optional and shares your visit and ad identifiers with X. Both are off until you allow them. Privacy policy.