Structured data
Structured data covers 20 of the SEO for Solos checklist's implementation checks. JSON-LD that describes the page to a machine, composed into one graph with stable identifiers rather than scattered across a dozen script tags.
0 of 20
Saved in this browser
20 checks shown.
Structured data
JSON-LD that describes the page to a machine, composed into one graph with stable identifiers rather than scattered across a dozen script tags.
One JSON-LD script per page, holding one @graph4/M
Why it matters
Multiple disconnected script tags describe several unrelated things. One graph describes one page, and lets nodes reference each other instead of repeating themselves.
How to verify
curl -s https://example.com/ | grep -c 'application/ld+json'Then confirm it prints 1.
Stable @id anchors that cross-reference rather than duplicate4/M
Why it matters
Without stable identifiers, every page ships its own copy of the Organization node and a crawler has no way to know they are the same entity. With them, one entity is asserted 2,000 times from 2,000 pages.
How to verify
Extract the graph and confirm every `{"@id": ...}` reference resolves to a node declared in the same document: `curl -s https://example.com/ | grep -o '<script type="application/ld+json">[^<]*' | sed 's/.*json">//' | jq '[..|."@id"?|strings]|unique'`.
Optional fields are omitted, never emitted as empty strings3/S
Why it matters
`"author": ""` asserts that the author is nothing. Omission asserts nothing at all, which is the honest and the machine-friendly state.
How to verify
Pipe the graph through `jq '[..|select(type=="string" and .=="")]|length'` and confirm it prints 0.
Organization node with logo, and sameAs listing only profiles that exist3/S
Why it matters
The Organization node is the anchor every other node points at. An invented social profile in sameAs is a broken entity claim that is trivially checked.
How to verify
Paste the URL into Google's Rich Results Test and confirm the Organization is detected with no errors. Then open every sameAs URL and confirm each one resolves.
WebSite node with publisher and, where a search route exists, a SearchAction2/S
Why it matters
The WebSite node is the isPartOf target for every page node, which is what ties a 2,000-page site together as one property rather than 2,000 documents.
How to verify
Confirm the WebSite node's publisher is an @id reference to the Organization node, not an inline copy of it.
BreadcrumbList on every page below the root, with the last item carrying no URL3/S
Why it matters
Breadcrumbs replace the URL in the search result, which is more readable and more clickable. The final item is the current page and must not carry an item URL, per Google's documented shape.
How to verify
Run the page through the Rich Results Test and confirm Breadcrumbs are detected with no warning about the last element.
Article schema with dateModified, wordCount, articleSection and a resolvable author3/M
Why it matters
dateModified is what makes a freshness signal legible. An author that is a bare string cannot be connected to anything; an author that is a Person node with a URL can.
How to verify
Rich Results Test on a blog post. Confirm datePublished, dateModified and author are all present and that author resolves to a node with a url.
FAQPage only where the questions are visible on the page3/S
Why it matters
Schema-only FAQs are a documented manual action risk. The remaining value is answer extraction by AI engines, which read the visible text anyway.
How to verify
For each question in the FAQPage node, confirm the exact string appears in the rendered HTML: `curl -s URL | grep -F "$QUESTION"`.
FAQ answers are in the HTML whether the accordion is open or closed3/S
Why it matters
An accordion that mounts its content on click ships an empty page to a crawler. A native details element ships the content either way.
How to verify
curl -s URL | grep -c '<details'Then confirm the answer text appears in the same curl output, with JavaScript never executed.
HowTo schema on procedural pages, with numbered steps matching the visible ones2/M
Why it matters
Procedural content is what answer engines quote most often. Numbered steps in schema make the boundaries of each step unambiguous to a parser.
How to verify
Rich Results Test on a tool page and confirm HowTo is detected with the same number of steps as the page renders.
Product and Offer schema generated from the pricing source of truth3/M
Why it matters
A price in schema that disagrees with the price on the page is a mismatch a shopping crawler will act on. Reading both from one module makes divergence impossible.
How to verify
Change a price in the pricing module, rebuild, and confirm the rendered price and the schema price both move: `curl -s /pricing | grep -o '"price":"[0-9.]*"'`.
Offer nodes carry priceCurrency, availability and a UnitPriceSpecification2/S
Why it matters
A price without a currency is not a price. UnitPriceSpecification is what distinguishes a one-time charge from a recurring one, which is the field buyers most often ask about.
How to verify
Pipe the graph through `jq '..|select(."@type"=="Offer")'` and confirm price, priceCurrency and availability are all present.
SoftwareApplication schema on every tool page, with a free Offer where the tool is free2/S
Why it matters
A free Offer with price 0 is what makes a free tool eligible for the treatment free tools get. Omitting it leaves the page describing an application with unknown cost.
How to verify
Rich Results Test on a tool page and confirm SoftwareApplication is detected with an offers node.
Service schema on service pages, distinct from Product1/S
Why it matters
A consulting engagement is not a product. Using Product for it produces a shopping-style entity claim that will never be satisfied.
How to verify
Confirm the service page's graph contains a Service node and no Product node.
DefinedTerm per glossary entry, inside one DefinedTermSet2/M
Why it matters
A glossary that emits 80 unconnected definitions is 80 orphans. A DefinedTermSet makes it one reference work with 80 parts.
How to verify
curl -s /glossary | jq '[."@graph"[]|select(."@type"=="DefinedTermSet")|.hasDefinedTerm|length]'Then confirm the count matches the term list length.
SpeakableSpecification naming the selectors that hold the canonical answer2/S
Why it matters
It costs one array of CSS selectors and it forces a useful discipline: you have to have a short, self-contained answer somewhere on the page to point it at.
How to verify
Confirm every selector in the speakable node matches an element in the rendered HTML, and that the matched text is a complete sentence.
ProfilePage and Person schema on author pages, with no invented credentials2/S
Why it matters
Author entities are a real E-E-A-T signal. A fabricated one is a liability, and omitted fields cost nothing.
How to verify
Confirm the author page emits ProfilePage with a mainEntity Person, and that every field present is verifiable.
ItemList on hub and index pages2/S
Why it matters
A hub page is a list. Describing it as one tells a crawler the page's purpose is navigation, which is what it is.
How to verify
Confirm the hub page's graph contains an ItemList whose numberOfItems matches the rendered count.
No @id is declared twice in one graph3/S
Why it matters
Two nodes with the same identifier is a validation error and an ambiguous entity claim. It happens the moment two composers both add the Organization node.
How to verify
Pipe the graph through `jq '[."@graph"[]."@id"]|group_by(.)|map(select(length>1))'` and confirm the result is empty.