<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: A.R. Hashir</title>
    <description>The latest articles on DEV Community by A.R. Hashir (@arhashir).</description>
    <link>https://dev.to/arhashir</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4204187%2F3db6084f-9d94-45aa-84a9-8376ac92a09a.png</url>
      <title>DEV Community: A.R. Hashir</title>
      <link>https://dev.to/arhashir</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/arhashir"/>
    <language>en</language>
    <item>
      <title>The AI Search Audit: 8 Checks, 100 Points, One Script</title>
      <dc:creator>A.R. Hashir</dc:creator>
      <pubDate>Sat, 24 Oct 2026 11:03:18 +0000</pubDate>
      <link>https://dev.to/arhashir/the-ai-search-audit-8-checks-100-points-one-script-18jp</link>
      <guid>https://dev.to/arhashir/the-ai-search-audit-8-checks-100-points-one-script-18jp</guid>
      <description>&lt;p&gt;Your analytics flattened in March. Organic clicks are down 30%, then 40%. You shipped nothing wrong. Rankings look stable. The traffic just isn't arriving.&lt;/p&gt;

&lt;p&gt;Here is what most teams do next: they buy a "GEO audit," add more schema, publish more content. Six months later the numbers are worse and nobody can point at a single decision that caused it.&lt;/p&gt;

&lt;p&gt;The reason is almost never the thing being optimized. It's that three technical gates were failing the entire time, and every downstream fix was being applied to a page that a crawler could not fully read.&lt;/p&gt;

&lt;p&gt;This is a scored audit for finding that. Eight categories, 100 points, and one script that runs most of it in about thirty seconds.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this got harder in 2026, in numbers
&lt;/h2&gt;

&lt;p&gt;Three data points worth internalizing before the checklist:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;68% of Google searches now end without a click.&lt;/strong&gt; That is up from roughly 45% a decade ago. Search volume is at an all-time high and still growing — the capture rate collapsed, not the demand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When an AI Overview appears, click-through drops by about 61%.&lt;/strong&gt; AI Overviews trigger on a growing share of informational queries. The top organic result still exists; people just stop there less often.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Only 8% of visits click a traditional result when an AI summary is present, versus 15% when it isn't.&lt;/strong&gt; Users read the summary. They click the citation occasionally. They click the blue link rarely.&lt;/p&gt;

&lt;p&gt;One number moves in the opposite direction, and it's the one that matters for this audit: &lt;strong&gt;AI-referred visitors convert at roughly 3x the rate of traditional organic traffic.&lt;/strong&gt; Similarweb measured ChatGPT-referred sessions at about 15 minutes versus 8 for Google.&lt;/p&gt;

&lt;p&gt;So the shape of the opportunity changed. Fewer clicks, better clicks. Which means eligibility is now worth more than volume — a page that can be retrieved and cited is worth several that cannot.&lt;/p&gt;

&lt;p&gt;That reframes the work. You are not chasing rankings. You are making your content &lt;strong&gt;retrievable, quotable, and unambiguous&lt;/strong&gt;. That's a technical problem before it's a writing problem, and it's the subject of this audit.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 40/60 rule: why most audits start in the wrong place
&lt;/h2&gt;

&lt;p&gt;Here is the single most useful thing in this article.&lt;/p&gt;

&lt;p&gt;The eight categories below are not weighted equally, and two of them are &lt;strong&gt;blocking&lt;/strong&gt;. If Categories 1 and 2 fail, the other six are decoration.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Points&lt;/th&gt;
&lt;th&gt;Blocking&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;01&lt;/td&gt;
&lt;td&gt;Crawlability&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;02&lt;/td&gt;
&lt;td&gt;Renderability&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;03&lt;/td&gt;
&lt;td&gt;Entity clarity&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;04&lt;/td&gt;
&lt;td&gt;Structured data&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;05&lt;/td&gt;
&lt;td&gt;Content chunking&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;06&lt;/td&gt;
&lt;td&gt;Freshness &amp;amp; trust&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;07&lt;/td&gt;
&lt;td&gt;Answer formatting&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;08&lt;/td&gt;
&lt;td&gt;Measurement&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Categories 01 and 02 are 40% of the score and they gate everything downstream. &lt;strong&gt;If those two come in under 32 out of 40, stop. Fix rendering and robots.txt this week. Do not spend a single hour on schema.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I have watched teams spend a quarter perfecting &lt;code&gt;sameAs&lt;/code&gt; arrays and entity graphs while their documentation body copy was assembled in the browser after hydration. The audit spreadsheet looked excellent. The crawler was receiving an empty shell.&lt;/p&gt;

&lt;p&gt;That is the 40/60 rule. Apply it before you read further.&lt;/p&gt;




&lt;h2&gt;
  
  
  Category 01 — Crawlability (20 points)
&lt;/h2&gt;

&lt;p&gt;Can a non-browser client reach and read your URLs at all? This is binary, and everything else is downstream of it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Pts&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;robots.txt&lt;/code&gt; returns HTTP 200 and contains no wildcard &lt;code&gt;Disallow: /&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;OAI-SearchBot&lt;/code&gt; explicitly allowed (or deliberately blocked, and documented)&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Googlebot&lt;/code&gt; allowed on every route that matters&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;PerplexityBot&lt;/code&gt; explicitly allowed (or deliberately blocked)&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Bingbot&lt;/code&gt; allowed — Copilot grounding depends on the Bing index&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sitemap exists at &lt;code&gt;/sitemap.xml&lt;/code&gt; and is referenced from &lt;code&gt;robots.txt&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sitemap contains only canonical, indexable, 200-status URLs&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No WAF, bot challenge or CDN rule returns 403/429/503 to AI user agents&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  The silent failure that costs entire channels
&lt;/h3&gt;

&lt;p&gt;A CDN default that 403s &lt;code&gt;PerplexityBot&lt;/code&gt; costs you the whole channel and produces zero errors anywhere. No deploy, no alert, no log line you'll notice. This is the most common cause of "we optimized for AI search and nothing happened."&lt;/p&gt;

&lt;p&gt;Four lines of shell settles it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;ua &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"GPTBot/1.1"&lt;/span&gt; &lt;span class="s2"&gt;"OAI-SearchBot/1.0"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
          &lt;span class="s2"&gt;"PerplexityBot/1.0"&lt;/span&gt; &lt;span class="s2"&gt;"ClaudeBot/1.0"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nv"&gt;code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-A&lt;/span&gt; &lt;span class="s2"&gt;"Mozilla/5.0 (compatible; &lt;/span&gt;&lt;span class="nv"&gt;$ua&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt; https://yoursite.com/&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ua&lt;/span&gt;&lt;span class="s2"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="nv"&gt;$code&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four &lt;code&gt;200&lt;/code&gt;s, or you have found your first problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Know which crawler does what
&lt;/h3&gt;

&lt;p&gt;The robots.txt snippets circulating online are two years stale, and most conflate three different jobs into one decision. These are separate agents:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;th&gt;Operator&lt;/th&gt;
&lt;th&gt;Governs&lt;/th&gt;
&lt;th&gt;Worth blocking?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GPTBot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OpenAI&lt;/td&gt;
&lt;td&gt;Model training&lt;/td&gt;
&lt;td&gt;Optional&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OAI-SearchBot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OpenAI&lt;/td&gt;
&lt;td&gt;ChatGPT search citations&lt;/td&gt;
&lt;td&gt;Rarely&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ChatGPT-User&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OpenAI&lt;/td&gt;
&lt;td&gt;User-triggered live fetch&lt;/td&gt;
&lt;td&gt;Rarely&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ClaudeBot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Anthropic&lt;/td&gt;
&lt;td&gt;Training + crawling&lt;/td&gt;
&lt;td&gt;Optional&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Claude-SearchBot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Anthropic&lt;/td&gt;
&lt;td&gt;Search grounding&lt;/td&gt;
&lt;td&gt;Rarely&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PerplexityBot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Perplexity&lt;/td&gt;
&lt;td&gt;Search index&lt;/td&gt;
&lt;td&gt;Rarely&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Googlebot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Google&lt;/td&gt;
&lt;td&gt;Search + AI Overviews&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Never&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Google-Extended&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Google&lt;/td&gt;
&lt;td&gt;Gemini training only&lt;/td&gt;
&lt;td&gt;Optional&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Bingbot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Microsoft&lt;/td&gt;
&lt;td&gt;Bing + Copilot grounding&lt;/td&gt;
&lt;td&gt;Rarely&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CCBot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Common Crawl&lt;/td&gt;
&lt;td&gt;Upstream model data&lt;/td&gt;
&lt;td&gt;Optional&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two corrections that change real decisions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blocking &lt;code&gt;Google-Extended&lt;/code&gt; does not remove you from Google AI Overviews.&lt;/strong&gt; AI Overviews are served from the ordinary Google index, governed by &lt;code&gt;Googlebot&lt;/code&gt;. &lt;code&gt;Google-Extended&lt;/code&gt; only controls Gemini training.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blocking &lt;code&gt;GPTBot&lt;/code&gt; does not remove you from ChatGPT search results.&lt;/strong&gt; Citations come from &lt;code&gt;OAI-SearchBot&lt;/code&gt;, and live URL fetches use &lt;code&gt;ChatGPT-User&lt;/code&gt;. Three separate entries, three separate decisions.&lt;/p&gt;

&lt;p&gt;A coherent default for most commercial sites is: &lt;strong&gt;block training, allow search.&lt;/strong&gt; That is defensible and, more importantly, deliberate rather than copied.&lt;/p&gt;




&lt;h2&gt;
  
  
  Category 02 — Renderability (20 points)
&lt;/h2&gt;

&lt;p&gt;Does the content exist in the delivered HTML, or does it appear only after JavaScript runs? This is where modern frameworks quietly lose 15 points.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Pts&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Primary body copy present in raw HTML with JavaScript disabled&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;h1&lt;/code&gt;–&lt;code&gt;h3&lt;/code&gt; headings present in raw HTML&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Internal links are real &lt;code&gt;&amp;lt;a href&amp;gt;&lt;/code&gt; elements in raw HTML&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JSON-LD present in raw HTML, not injected after hydration&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Content survives &lt;code&gt;curl&lt;/code&gt; at 80%+ of the rendered word count&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# raw word count, tags stripped&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://yoursite.com/page | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/&amp;lt;[^&amp;gt;]*&amp;gt;//g'&lt;/span&gt; | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compare that against what the browser shows. Within 10% is fine. A 30% gap is a widget. &lt;strong&gt;A 3x gap means your content does not exist as far as retrieval is concerned&lt;/strong&gt; — no matter how clean your markup is.&lt;/p&gt;

&lt;h3&gt;
  
  
  The hydration trap
&lt;/h3&gt;

&lt;p&gt;This is the pattern that silently fails:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ invisible to most AI crawlers&lt;/span&gt;
&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;script&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/ld+json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;head&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this is the fix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ✅ present in the raw HTML response&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jsonLd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;
    &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/ld+json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="nx"&gt;dangerouslySetInnerHTML&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;__html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jsonLd&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
  &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify it in the only place that counts — not DevTools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://yoursite.com/page | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'application/ld+json'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;0&lt;/code&gt; means everything downstream of this section is theoretical.&lt;/p&gt;

&lt;p&gt;The practical answer is SSR, SSG, or ISR for every content route. Client-side rendering belongs to application state, not to published content.&lt;/p&gt;




&lt;h2&gt;
  
  
  Category 03 — Entity clarity (18 points)
&lt;/h2&gt;

&lt;p&gt;Retrieval systems resolve entities, not pages. Can a machine work out who you are, unambiguously, from any page on the site?&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Pts&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Exactly one &lt;code&gt;Organization&lt;/code&gt; (or &lt;code&gt;Person&lt;/code&gt;) node per page, consistent across pages&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;@id&lt;/code&gt; values used and cross-referenced between nodes&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;sameAs&lt;/code&gt; lists 3–8 live, populated profiles (no 404s, no empty accounts)&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same entity name in &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt;, &lt;code&gt;og:title&lt;/code&gt;, JSON-LD and visible body copy&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Organization node has a resolvable &lt;code&gt;logo&lt;/code&gt; ImageObject at an absolute URL&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No duplicate or conflicting entity blocks from competing plugins&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://yoursite.com/ | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s1"&gt;'"@type":"Organization"'&lt;/span&gt; | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;
&lt;span class="c"&gt;# expected: 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A return of &lt;code&gt;2&lt;/code&gt; means a plugin and a theme are both emitting an organization with different names or URLs. To a reconciling system that is two companies, and consolidation fails silently — no error, no warning, no dashboard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On &lt;code&gt;sameAs&lt;/code&gt; discipline:&lt;/strong&gt; every URL must return 200 and carry real content. A dead profile is worse than no profile, because you asserted a fact that then failed verification. Three to eight links is the useful range. Forty reads as manipulation and gets discounted.&lt;/p&gt;




&lt;h2&gt;
  
  
  Category 04 — Structured data (15 points)
&lt;/h2&gt;

&lt;p&gt;Validators check shape. They do not check meaning, recency, or whether the markup agrees with the page. That gap is where these 15 points live.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Pts&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Every JSON-LD block parses with zero syntax errors on priority URLs&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;datePublished&lt;/code&gt; / &lt;code&gt;dateModified&lt;/code&gt; in ISO 8601&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Visible page content matches every claim the markup makes&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;@type&lt;/code&gt; values correctly cased (&lt;code&gt;WebPage&lt;/code&gt;, not &lt;code&gt;Webpage&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;All &lt;code&gt;image&lt;/code&gt; values are absolute &lt;code&gt;https://&lt;/code&gt; URLs&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;FAQPage&lt;/code&gt; uses nested &lt;code&gt;Question&lt;/code&gt; → &lt;code&gt;acceptedAnswer&lt;/code&gt; → &lt;code&gt;Answer&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Values that parse but degrade output
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;Common value&lt;/th&gt;
&lt;th&gt;Correct value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;datePublished&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Oct 24, 2026&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2026-10-24&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;image&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/cover.png&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://yoursite.com/cover.png&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@type&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Webpage&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;WebPage&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;`Brand \&lt;/td&gt;
&lt;td&gt;Best Tools 2026`&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;description&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;400-character paragraph&lt;/td&gt;
&lt;td&gt;150–160 chars, one sentence&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;"Oct 24, 2026"&lt;/code&gt; is ambiguous enough that some parsers drop the field entirely, and &lt;code&gt;dateModified&lt;/code&gt; is one of the few freshness signals you fully control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The row people skip is row three.&lt;/strong&gt; If your markup asserts an answer the page does not visibly give, you have manufactured a confident wrong answer attributed to your brand. That is worse than silence, because it will be quoted.&lt;/p&gt;

&lt;p&gt;And one tool used for the wrong job: &lt;strong&gt;the Rich Results Test validates syntax. Syntax was never the failure mode.&lt;/strong&gt; Use it as a lint step, not a diagnosis.&lt;/p&gt;




&lt;h2&gt;
  
  
  Category 05 — Content chunking (12 points)
&lt;/h2&gt;

&lt;p&gt;Retrieval does not read your page top to bottom. It splits the page along heading and paragraph boundaries, embeds each piece, and fetches whichever pieces match. &lt;strong&gt;Write for the piece.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Pts&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Each H2 section is self-contained and makes sense in isolation&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Each section opens with a direct answer in the first one or two sentences&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No context-dependent openers ("as mentioned above", a bare "this")&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Paragraphs under roughly 120 words&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Key claims exist as text, not only inside images&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  The isolation test
&lt;/h3&gt;

&lt;p&gt;Copy a single H2 section into a fresh chat with no surrounding page. If it makes sense and answers something, it survives chunking. If it opens with a pronoun pointing three sections up, it does not — and that chunk will never be retrieved no matter how good the page is as a whole.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;Survives&lt;/th&gt;
&lt;th&gt;Does not&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Opening&lt;/td&gt;
&lt;td&gt;"JSON-LD works by placing a self-contained entity graph…"&lt;/td&gt;
&lt;td&gt;"It works by wrapping the entity, as mentioned above…"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scope&lt;/td&gt;
&lt;td&gt;Names the subject in the first clause&lt;/td&gt;
&lt;td&gt;Relies on the H1 for context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Length&lt;/td&gt;
&lt;td&gt;One idea, under 120 words&lt;/td&gt;
&lt;td&gt;Four ideas in one 300-word block&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This category costs nothing to fix and is the one most often skipped. It is a writing-discipline change, not an engineering one — no deploy, no ticket, no framework upgrade.&lt;/p&gt;




&lt;h2&gt;
  
  
  Category 06 — Freshness &amp;amp; trust (8 points)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Pts&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Long-lived pages show a visible "last updated" date&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;dateModified&lt;/code&gt; reflects real edits, not a mechanical bump&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Author or organization attribution visible on-page&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fact-heavy sections cite external sources with working links&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nothing material is stale in a fast-moving topic&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A 2023 page about 2026 crawler behaviour is not merely unranked — it gets retrieved, and it is wrong. Archive or update. Do not leave it up hoping.&lt;/p&gt;




&lt;h2&gt;
  
  
  Category 07 — Answer formatting (5 points)
&lt;/h2&gt;

&lt;p&gt;Deliberately the lowest-weighted category, because it is the easiest and everyone does it first.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Pts&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Summary or TL;DR block near the top of long pages&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Comparison tables for "X vs Y" queries&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Procedural content uses ordered steps&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Formatting makes content easy to extract, so it feels productive. It is worth 5 points and will not rescue a site scoring 20 on the first two categories.&lt;/p&gt;




&lt;h2&gt;
  
  
  Category 08 — Measurement (2 points)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Pts&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Server logs accessible, AI crawler user agents filterable&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Five target questions probed weekly across ChatGPT, Perplexity and Google&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iE&lt;/span&gt; &lt;span class="s2"&gt;"GPTBot|OAI-SearchBot|ClaudeBot|PerplexityBot"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  /var/log/nginx/access.log &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $9}'&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things fall out in ninety seconds: whether these crawlers visit at all, which URLs they actually pull, and whether they receive 200 or bounce off a 403. If the answer to the first is no, the rest of your plan is theoretical.&lt;/p&gt;




&lt;h2&gt;
  
  
  The whole audit in one script
&lt;/h2&gt;

&lt;p&gt;Here is every automatable check above, in a single file. Save it as &lt;code&gt;audit.sh&lt;/code&gt;, make it executable, and pass one URL.&lt;/p&gt;

&lt;p&gt;It does not cover Categories 03 through 07 completely — those need human judgment — but it will find the blocking failures, which is where you should start.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# ai-search-audit.sh — blocking checks only&lt;/span&gt;
&lt;span class="c"&gt;# usage: ./audit.sh https://yoursite.com/page&lt;/span&gt;

&lt;span class="nv"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;:?usage:&lt;span class="p"&gt; ./audit.sh &amp;lt;url&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;BASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'s#(https?://[^/]+).*#\1#'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;PASS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;FAIL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0

ok&lt;span class="o"&gt;()&lt;/span&gt;   &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  PASS  &lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;PASS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;PASS+1&lt;span class="k"&gt;))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
bad&lt;span class="o"&gt;()&lt;/span&gt;  &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  FAIL  &lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;FAIL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;FAIL+1&lt;span class="k"&gt;))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"== 01 CRAWLABILITY =="&lt;/span&gt;

&lt;span class="nv"&gt;ROBOTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;/robots.txt"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ROBOTS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"200"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ok &lt;span class="s2"&gt;"robots.txt returns 200"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
                      &lt;span class="o"&gt;||&lt;/span&gt; bad &lt;span class="s2"&gt;"robots.txt returned &lt;/span&gt;&lt;span class="nv"&gt;$ROBOTS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;/robots.txt"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-qiE&lt;/span&gt; &lt;span class="s1"&gt;'^Disallow: *\*? *$|^Disallow: */$'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;bad &lt;span class="s2"&gt;"robots.txt contains a wildcard Disallow"&lt;/span&gt;
&lt;span class="k"&gt;else
  &lt;/span&gt;ok &lt;span class="s2"&gt;"no wildcard Disallow in robots.txt"&lt;/span&gt;
&lt;span class="k"&gt;fi

for &lt;/span&gt;ua &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"GPTBot/1.1"&lt;/span&gt; &lt;span class="s2"&gt;"OAI-SearchBot/1.0"&lt;/span&gt; &lt;span class="s2"&gt;"PerplexityBot/1.0"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
          &lt;span class="s2"&gt;"ClaudeBot/1.0"&lt;/span&gt; &lt;span class="s2"&gt;"Googlebot/2.1"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nv"&gt;code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
          &lt;span class="nt"&gt;-A&lt;/span&gt; &lt;span class="s2"&gt;"Mozilla/5.0 (compatible; &lt;/span&gt;&lt;span class="nv"&gt;$ua&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ua&lt;/span&gt;&lt;span class="p"&gt;%%/*&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$code&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"200"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then &lt;/span&gt;ok &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="s2"&gt; reaches the page (200)"&lt;/span&gt;
  &lt;span class="k"&gt;else &lt;/span&gt;bad &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="s2"&gt; got &lt;/span&gt;&lt;span class="nv"&gt;$code&lt;/span&gt;&lt;span class="s2"&gt; — blocked by WAF/CDN?"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;fi
done

&lt;/span&gt;&lt;span class="nv"&gt;SM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;/sitemap.xml"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SM&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"200"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ok &lt;span class="s2"&gt;"sitemap.xml returns 200"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
                  &lt;span class="o"&gt;||&lt;/span&gt; bad &lt;span class="s2"&gt;"sitemap.xml returned &lt;/span&gt;&lt;span class="nv"&gt;$SM&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;echo
echo&lt;/span&gt; &lt;span class="s2"&gt;"== 02 RENDERABILITY =="&lt;/span&gt;

&lt;span class="nv"&gt;RAW&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;RAW_WORDS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RAW&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/&amp;lt;[^&amp;gt;]*&amp;gt;//g'&lt;/span&gt; | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RAW_WORDS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-ge&lt;/span&gt; 300 &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ok &lt;span class="s2"&gt;"raw HTML has &lt;/span&gt;&lt;span class="nv"&gt;$RAW_WORDS&lt;/span&gt;&lt;span class="s2"&gt; words"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
                         &lt;span class="o"&gt;||&lt;/span&gt; bad &lt;span class="s2"&gt;"raw HTML has only &lt;/span&gt;&lt;span class="nv"&gt;$RAW_WORDS&lt;/span&gt;&lt;span class="s2"&gt; words — JS-rendered?"&lt;/span&gt;

&lt;span class="nv"&gt;LD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RAW&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'application/ld+json'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-ge&lt;/span&gt; 1 &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ok &lt;span class="s2"&gt;"JSON-LD present in raw HTML (&lt;/span&gt;&lt;span class="nv"&gt;$LD&lt;/span&gt;&lt;span class="s2"&gt; block(s))"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
                &lt;span class="o"&gt;||&lt;/span&gt; bad &lt;span class="s2"&gt;"no JSON-LD in raw HTML — injected after hydration?"&lt;/span&gt;

&lt;span class="nv"&gt;H1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RAW&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;h1'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$H1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-ge&lt;/span&gt; 1 &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ok &lt;span class="s2"&gt;"h1 present in raw HTML"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
                &lt;span class="o"&gt;||&lt;/span&gt; bad &lt;span class="s2"&gt;"no h1 in raw HTML"&lt;/span&gt;

&lt;span class="nv"&gt;LINKS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RAW&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s1"&gt;'href="http'&lt;/span&gt; | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LINKS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-ge&lt;/span&gt; 5 &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ok &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LINKS&lt;/span&gt;&lt;span class="s2"&gt; absolute internal/external links in raw HTML"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
                   &lt;span class="o"&gt;||&lt;/span&gt; bad &lt;span class="s2"&gt;"only &lt;/span&gt;&lt;span class="nv"&gt;$LINKS&lt;/span&gt;&lt;span class="s2"&gt; links in raw HTML"&lt;/span&gt;

&lt;span class="nb"&gt;echo
echo&lt;/span&gt; &lt;span class="s2"&gt;"== 04 STRUCTURED DATA (syntax) =="&lt;/span&gt;

&lt;span class="nv"&gt;ORG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RAW&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s1"&gt;'"@type":"Organization"'&lt;/span&gt; | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ORG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 1 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then &lt;/span&gt;ok &lt;span class="s2"&gt;"exactly one Organization node"&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ORG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then &lt;/span&gt;bad &lt;span class="s2"&gt;"no Organization node found"&lt;/span&gt;
&lt;span class="k"&gt;else &lt;/span&gt;bad &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ORG&lt;/span&gt;&lt;span class="s2"&gt; Organization nodes — duplicate entity data"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RAW&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-qE&lt;/span&gt; &lt;span class="s1"&gt;'"date[A-Za-z]+":"[0-9]{4}-[0-9]{2}-[0-9]{2}"'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ok &lt;span class="s2"&gt;"ISO 8601 dates present"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; bad &lt;span class="s2"&gt;"no ISO 8601 date fields detected"&lt;/span&gt;

&lt;span class="nb"&gt;echo
echo&lt;/span&gt; &lt;span class="s2"&gt;"== RESULT =="&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  passed: &lt;/span&gt;&lt;span class="nv"&gt;$PASS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  failed: &lt;/span&gt;&lt;span class="nv"&gt;$FAIL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FAIL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  blocking categories look clear."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
                  &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  fix the FAILs above before touching schema."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it. If anything prints &lt;code&gt;FAIL&lt;/code&gt; in the first two sections, you have your work order for the week and the other six categories can wait.&lt;/p&gt;




&lt;h2&gt;
  
  
  Your score sheet
&lt;/h2&gt;

&lt;p&gt;Fill this in after running the script and scoring the human-judgment rows.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Max&lt;/th&gt;
&lt;th&gt;Yours&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;01 · Crawlability&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;02 · Renderability&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;03 · Entity clarity&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;04 · Structured data&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;05 · Content chunking&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;06 · Freshness &amp;amp; trust&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;07 · Answer formatting&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;08 · Measurement&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TOTAL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  What your score means
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;85–100&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Eligible. Remaining gap is content quality, not technical&lt;/td&gt;
&lt;td&gt;Stop auditing. Write better pages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;60–84&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Partially eligible. Citations will be inconsistent&lt;/td&gt;
&lt;td&gt;Close 01–02 first, then 03–04&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;35–59&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Mostly invisible. Likely blocked, JS-only, or unparseable&lt;/td&gt;
&lt;td&gt;Everything else is wasted until 01–02 pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0–34&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not reachable. Do not buy tooling yet&lt;/td&gt;
&lt;td&gt;Fix rendering and robots.txt this week&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Fastest wins, ranked by minutes-to-impact
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Typical points&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Unblock AI UAs in robots.txt / WAF&lt;/td&gt;
&lt;td&gt;15 min&lt;/td&gt;
&lt;td&gt;up to 8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ISO-format all dates&lt;/td&gt;
&lt;td&gt;20 min&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Remove &lt;code&gt;display:none&lt;/code&gt;-only body content&lt;/td&gt;
&lt;td&gt;30 min&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deduplicate Organization blocks&lt;/td&gt;
&lt;td&gt;45 min&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Add an &lt;code&gt;@graph&lt;/code&gt; with &lt;code&gt;@id&lt;/code&gt; cross-references&lt;/td&gt;
&lt;td&gt;2 hrs&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSR/ISR the main content routes&lt;/td&gt;
&lt;td&gt;1–3 days&lt;/td&gt;
&lt;td&gt;up to 20&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The last row is the only expensive one, and it is also the only one that can move a site from "not reachable" to "eligible" on its own.&lt;/p&gt;




&lt;h2&gt;
  
  
  Three things that do not do what you think
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. The Rich Results Test.&lt;/strong&gt; It validates syntax. Syntax was never the failure mode. Use it as a lint step before deploying, not as a diagnosis of why you are not cited.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. &lt;code&gt;llms.txt&lt;/code&gt;.&lt;/strong&gt; A proposal published in late 2024, not a standard, with no major AI provider having confirmed publicly that it reads the file. Adding one costs twenty minutes and breaks nothing. Treating it as the fix is a mistake. If someone tells you otherwise, ask which provider confirmed it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Adding more schema types.&lt;/strong&gt; If you already have a clean &lt;code&gt;Organization&lt;/code&gt; node and a correct &lt;code&gt;Article&lt;/code&gt; node and are still uncited, more types will not help. At that point the constraint is whether your content is the clearest available answer to a specific question — which is a writing problem, not a markup problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does any of this guarantee I'll be cited?&lt;/strong&gt;&lt;br&gt;
No. It makes you eligible. Selection depends on whether your content is the clearest, most specific answer available for a given question. That is a writing problem, and no audit solves it for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do AI crawlers run JavaScript?&lt;/strong&gt;&lt;br&gt;
Mostly no, or unreliably. Googlebot renders. The rest generally fetch raw HTML. Build on the assumption that JS-only content does not exist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Should I block GPTBot?&lt;/strong&gt;&lt;br&gt;
Decide per purpose, not wholesale. Blocking &lt;code&gt;GPTBot&lt;/code&gt; (training) does not affect ChatGPT search. Blocking &lt;code&gt;OAI-SearchBot&lt;/code&gt; does. Blocking &lt;code&gt;Google-Extended&lt;/code&gt; does not affect AI Overviews. Three separate decisions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is &lt;code&gt;llms.txt&lt;/code&gt; supported by anyone?&lt;/strong&gt;&lt;br&gt;
Not confirmed by any major provider as of writing. Harmless to add, wrong to rely on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How often do these crawlers revisit?&lt;/strong&gt;&lt;br&gt;
Popular, frequently updated pages within days. Low-traffic pages within weeks to months. Your own server logs are the only reliable answer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What score should a small site target?&lt;/strong&gt;&lt;br&gt;
80+. Sites below 60 usually have one or two blocking issues, not a hundred small ones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How long until structured data shows an effect?&lt;/strong&gt;&lt;br&gt;
Recrawl cadence is the limiter, not the markup. Expect a quarter, not a week, and track server-log bot hits rather than inferring from traffic.&lt;/p&gt;




&lt;h2&gt;
  
  
  What to do this week
&lt;/h2&gt;

&lt;p&gt;Score all eight categories honestly, without giving yourself the benefit of the doubt. "Probably fine" is a zero.&lt;/p&gt;

&lt;p&gt;If Categories 01 and 02 come in under 32, fix rendering and robots.txt before anything else. Deploy one &lt;code&gt;Organization&lt;/code&gt; graph and verify it with &lt;code&gt;curl&lt;/code&gt;, not with DevTools.&lt;/p&gt;

&lt;p&gt;Then write down five questions your site should be the answer to, and probe them weekly in ChatGPT, Perplexity and Google. Log which URL gets cited. Over a month, that log tells you more than any audit tool will.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you want this audit as a scored checklist you can actually fill in — 20 pages, all eight categories with every row and point value, plus three production-ready JSON-LD templates (&lt;code&gt;Organization&lt;/code&gt; + &lt;code&gt;WebSite&lt;/code&gt;, &lt;code&gt;Article&lt;/code&gt; + &lt;code&gt;Person&lt;/code&gt;, &lt;code&gt;FAQPage&lt;/code&gt;) wired with &lt;code&gt;@id&lt;/code&gt; cross-references — it's free: *&lt;/em&gt;&lt;a href="https://abrahman2.gumroad.com/l/ai-search-readiness-checklist" rel="noopener noreferrer"&gt;100-Point AI Search Readiness Checklist&lt;/a&gt;***&lt;/p&gt;

&lt;p&gt;&lt;em&gt;And if you want the full build rather than the audit — the complete template set, the raw-vs-rendered diff tooling, the log-analysis queries, and the rewrite patterns for making each section survive chunking — that's the reference: *&lt;/em&gt;&lt;a href="https://abrahman2.gumroad.com/l/structured-for-answers" rel="noopener noreferrer"&gt;Structured for Answers →&lt;/a&gt;***&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Earlier in this series: &lt;a href="https://dev.to/arhashir/json-ld-for-ai-search-7-mistakes-that-block-citations-1ea6"&gt;JSON-LD for AI Search: 7 Mistakes That Block Citations&lt;/a&gt; — why valid markup can still leave you uncited.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>seo</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>JSON-LD for AI Search: 7 Mistakes That Block Citations</title>
      <dc:creator>A.R. Hashir</dc:creator>
      <pubDate>Sat, 24 Oct 2026 10:00:21 +0000</pubDate>
      <link>https://dev.to/arhashir/json-ld-for-ai-search-7-mistakes-that-block-citations-1ea6</link>
      <guid>https://dev.to/arhashir/json-ld-for-ai-search-7-mistakes-that-block-citations-1ea6</guid>
      <description>&lt;p&gt;Most structured data audits end the same way: the validator reports zero errors, the Rich Results Test passes, and six months later the site has still never appeared in a single ChatGPT or Perplexity answer.&lt;/p&gt;

&lt;p&gt;I have been through this on enough sites to say the uncomfortable part plainly — &lt;strong&gt;the problem is almost never syntax.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It is that the JSON-LD describes &lt;em&gt;a page&lt;/em&gt; while the retrieval system is looking for &lt;em&gt;an entity&lt;/em&gt;. And it is that the page's actual content never reaches the crawler in the first place, so the markup is describing something that was never read.&lt;/p&gt;

&lt;p&gt;Seven failures, ordered by what they cost you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The short version, if you read nothing else:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If &lt;code&gt;WebPage&lt;/code&gt; is your only node, you described a container, not a publisher.&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;sameAs&lt;/code&gt; is missing or points at a 404, the entity cannot be resolved.&lt;/li&gt;
&lt;li&gt;If your JSON-LD is injected after hydration, most AI crawlers never see it.&lt;/li&gt;
&lt;li&gt;If your body copy renders client-side, nothing else in this article matters.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rest of this is the long version of those four lines.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. You described the page, not the entity
&lt;/h2&gt;

&lt;p&gt;The default pattern is a single &lt;code&gt;WebPage&lt;/code&gt; block. That tells a machine "this is a page of type WebPage" — which is roughly as useful as telling it "this is a file."&lt;/p&gt;

&lt;p&gt;Retrieval and grounding systems need to resolve &lt;strong&gt;who you are&lt;/strong&gt;, not which tag wraps the URL. There is no path from &lt;code&gt;WebPage&lt;/code&gt; to a knowledge graph node, because nothing in that block claims to be a thing in the world.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://schema.org"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WebPage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Acme Analytics — Product Page"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://acme.dev/product"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://schema.org"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@graph"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Organization"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://acme.dev/#organization"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Acme Analytics"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://acme.dev/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"logo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ImageObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://acme.dev/logo-512.png"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sameAs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"https://github.com/acme-analytics"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"https://www.crunchbase.com/organization/acme-analytics"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SoftwareApplication"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://acme.dev/product#app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Acme Analytics"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"applicationCategory"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"BusinessApplication"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"operatingSystem"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Web"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"publisher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"@id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://acme.dev/#organization"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Privacy-first web analytics with cookieless session tracking."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://acme.dev/product"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WebPage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://acme.dev/product#webpage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://acme.dev/product"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"about"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"@id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://acme.dev/product#app"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;@id&lt;/code&gt; values are the actual work. They turn three disconnected blocks into one addressable graph, so any node can be referenced from anywhere on the site without repeating the data inside it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost of skipping this:&lt;/strong&gt; the page can be indexed and still be unciteable, because there is no unambiguous subject to attribute the claim to.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. &lt;code&gt;sameAs&lt;/code&gt; is missing, or points at dead profiles
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;sameAs&lt;/code&gt; is the cheapest disambiguation signal that exists, and it is the one most often skipped.&lt;/p&gt;

&lt;p&gt;An LLM asked "what is Acme Analytics" has no way to know that your domain is the authority on Acme Analytics. It &lt;strong&gt;does&lt;/strong&gt; know what a GitHub organisation, a Crunchbase entry, and a Wikidata item are. &lt;code&gt;sameAs&lt;/code&gt; is how you hand it the bridge between the two.&lt;/p&gt;

&lt;p&gt;Four rules that matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Only link profiles that exist and are populated.&lt;/strong&gt; A 404, a private repo, or a profile with no bio is worse than no link — it reads as a failed verification.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wikipedia and Wikidata beat everything else.&lt;/strong&gt; If you have either, that is your strongest edge and it belongs first in the array.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Three to eight links is the working range.&lt;/strong&gt; Forty looks like spam and gets discounted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never invent one.&lt;/strong&gt; A broken &lt;code&gt;sameAs&lt;/code&gt; is a trust signal pointing the wrong direction.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cost of skipping this:&lt;/strong&gt; every query about your brand becomes a coin flip between your site and someone else's, and whoever has the cleaner identity graph wins.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Your JSON-LD is injected after hydration
&lt;/h2&gt;

&lt;p&gt;This is the quiet killer on Next.js, Nuxt, React SPAs, and anything using a head-management library.&lt;/p&gt;

&lt;p&gt;If this is how your schema reaches the page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// invisible to most AI crawlers&lt;/span&gt;
&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;script&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;script&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/ld+json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;head&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;…then the crawler has to execute JavaScript, &lt;em&gt;and&lt;/em&gt; wait for hydration, &lt;em&gt;and&lt;/em&gt; wait for your effect to flush before the markup exists at all. Googlebot will sometimes manage it. Most AI crawlers will not.&lt;/p&gt;

&lt;p&gt;The fix is boring: serialize the JSON server-side and render it as static text in the document.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// present in the raw HTML response&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jsonLd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;
    &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/ld+json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="nx"&gt;dangerouslySetInnerHTML&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;__html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jsonLd&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
  &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then verify it the only way that counts — not in DevTools, in the actual response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://acme.dev/product | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'application/ld+json'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that returns &lt;code&gt;0&lt;/code&gt; while your browser shows perfectly valid schema, you have found your bug.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost of skipping this:&lt;/strong&gt; you keep maintaining markup that no retrieval pipeline has ever parsed.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Your content only exists after JavaScript runs
&lt;/h2&gt;

&lt;p&gt;Same root cause as above, much larger consequence. Schema is metadata. &lt;strong&gt;Content is what gets embedded and cited.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If your product copy, documentation body, or article text renders client-side, you are serving an empty shell to every crawler that does not run a browser.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# what a crawler sees vs what a user sees&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-A&lt;/span&gt; &lt;span class="s2"&gt;"GPTBot"&lt;/span&gt; https://acme.dev/docs/getting-started | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://acme.dev/docs/getting-started | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A 30-word difference is a widget. A &lt;strong&gt;900-word difference&lt;/strong&gt; means your documentation does not exist as far as retrieval is concerned — no matter how clean the JSON-LD on top of it is.&lt;/p&gt;

&lt;p&gt;The practical answer is SSR, SSG, or ISR for every route you want cited. Client-side rendering is for application state, not published content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost of skipping this:&lt;/strong&gt; the largest one on this list, and the only one that silently caps your ceiling no matter what you fix afterward.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Valid structure, broken values
&lt;/h2&gt;

&lt;p&gt;Validators check shape, not meaning. Every row below passes validation and still degrades the output.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Broken&lt;/th&gt;
&lt;th&gt;Fixed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;datePublished&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"Oct 24, 2026"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"2026-10-24"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;image&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"/cover.png"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"https://acme.dev/cover.png"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@type&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"Webpage"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"WebPage"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@type&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;"Article "&lt;/code&gt; (trailing space)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"Article"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;`"Acme Analytics \&lt;/td&gt;
&lt;td&gt;Best Tools"`&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;description&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;400-character paragraph&lt;/td&gt;
&lt;td&gt;one sentence, 150–160 chars&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;ISO dates matter more than they look. &lt;code&gt;"Oct 24, 2026"&lt;/code&gt; is ambiguous enough that some parsers drop the field entirely, and &lt;code&gt;dateModified&lt;/code&gt; is one of the few freshness signals you fully control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost of skipping this:&lt;/strong&gt; a validator that says "0 errors" while the values quietly fail to resolve.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. &lt;code&gt;FAQPage&lt;/code&gt; used as a keyword dump
&lt;/h2&gt;

&lt;p&gt;Two separate mistakes get made here constantly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mistake A — malformed structure.&lt;/strong&gt; &lt;code&gt;mainEntity&lt;/code&gt; must be an array of &lt;code&gt;Question&lt;/code&gt; objects, each with a nested &lt;code&gt;acceptedAnswer&lt;/code&gt; of type &lt;code&gt;Answer&lt;/code&gt;. A flat &lt;code&gt;{"question": "...", "answer": "..."}&lt;/code&gt; is not Schema.org.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://schema.org"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FAQPage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mainEntity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Question"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Does JSON-LD improve AI search visibility?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"acceptedAnswer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Answer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JSON-LD does not directly improve rankings. It removes ambiguity about what a page and its publisher are, which helps retrieval systems select the page as a source."&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Mistake B — mismatched pairs.&lt;/strong&gt; The marked-up question has to be answered by the visible text directly below it. If the markup asks "How much does it cost?" and the visible answer is about onboarding, you have created a false pair. That is worse than no markup, because the system now has a confident wrong answer attributed to you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One honest caveat:&lt;/strong&gt; Google restricted FAQ rich results to authoritative government and health sites in 2023, and removed HowTo rich results entirely. This markup will not win you a SERP feature. What it still does is produce clean, self-contained question-answer chunks that retrieval pipelines can lift without parsing your surrounding prose. That is the reason to keep it — not the rich result.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost of skipping this:&lt;/strong&gt; nothing, if you skip it entirely. The cost is in doing it badly.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Conflicting entity data across pages
&lt;/h2&gt;

&lt;p&gt;Bolt on a plugin, add a second one a year later, and you end up with two &lt;code&gt;Organization&lt;/code&gt; blocks per page saying different things:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Organization"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Acme Ltd"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Organization"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Acme Analytics Inc."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.acme.dev"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To a system that reconciles entities, that is two companies. Consolidation fails silently, and you get no error anywhere.&lt;/p&gt;

&lt;p&gt;Pick canonical values once — exact legal name, exact canonical URL, exact logo — and use them in &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt;, &lt;code&gt;og:title&lt;/code&gt;, and every &lt;code&gt;Organization&lt;/code&gt; node on the site. Then dedupe and verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# should return exactly 1 per page&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://acme.dev/ | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s1"&gt;'"@type":"Organization"'&lt;/span&gt; | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Cost of skipping this:&lt;/strong&gt; partial credit on every entity query, forever, with no signal that anything is wrong.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 5-minute audit order
&lt;/h2&gt;

&lt;p&gt;If you do five things after reading this, do them in this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;curl&lt;/code&gt; your three most important URLs and confirm the body copy is there without JavaScript&lt;/li&gt;
&lt;li&gt;Count &lt;code&gt;application/ld+json&lt;/code&gt; blocks in the raw HTML — zero means everything else is moot&lt;/li&gt;
&lt;li&gt;Check that every &lt;code&gt;sameAs&lt;/code&gt; URL returns 200 and has real content on it&lt;/li&gt;
&lt;li&gt;Confirm &lt;code&gt;dateModified&lt;/code&gt; is ISO 8601 and actually recent&lt;/li&gt;
&lt;li&gt;Search for &lt;code&gt;"@type":"Organization"&lt;/code&gt; and make sure it appears once, with consistent values&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Notice that none of these involve the Rich Results Test. It tells you the markup is well-formed, which was never the question.&lt;/p&gt;




&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does adding JSON-LD get my site cited by ChatGPT?&lt;/strong&gt;&lt;br&gt;
Not on its own. Citation depends on whether the page is crawlable, whether its content is retrievable without JavaScript, and whether it is the clearest available answer to the question. Schema removes ambiguity about the publisher and the entity; it does not create content authority.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Should I block GPTBot?&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;GPTBot&lt;/code&gt; is OpenAI's training crawler. It is separate from &lt;code&gt;OAI-SearchBot&lt;/code&gt;, which handles ChatGPT search, and &lt;code&gt;ChatGPT-User&lt;/code&gt;, which fires when a user asks ChatGPT to fetch a URL. Blocking &lt;code&gt;GPTBot&lt;/code&gt; does not remove you from ChatGPT search results. Blocking &lt;code&gt;OAI-SearchBot&lt;/code&gt; does. Make that a deliberate decision rather than a copied robots.txt snippet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does &lt;code&gt;FAQPage&lt;/code&gt; markup still do anything?&lt;/strong&gt;&lt;br&gt;
Not for rich results on most sites. It still produces clean question-answer pairs that are trivial to chunk during retrieval, which is a real but modest benefit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How long until structured data shows an effect?&lt;/strong&gt;&lt;br&gt;
Recrawl cadence is the limiter, not the markup. Crawlers revisit popular pages within days and low-traffic pages within weeks to months. Expect a quarter, not a week, and track server-log bot hits rather than trying to infer it from traffic.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Start with the free one.&lt;/strong&gt; The scored version of this audit — eight categories, 100 points, every row and point value, plus three production-ready JSON-LD templates (&lt;code&gt;Organization&lt;/code&gt; + &lt;code&gt;WebSite&lt;/code&gt;, &lt;code&gt;Article&lt;/code&gt; + &lt;code&gt;Person&lt;/code&gt;, &lt;code&gt;FAQPage&lt;/code&gt;) already wired with &lt;code&gt;@id&lt;/code&gt; cross-references: &lt;strong&gt;&lt;a href="https://abrahman2.gumroad.com/l/ai-search-readiness-checklist" rel="noopener noreferrer"&gt;100-Point AI Search Readiness Checklist&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The full build.&lt;/strong&gt; If you want the complete template set, the raw-vs-rendered diff tooling, the log-analysis queries, and the rewrite patterns for making each section survive chunking: &lt;strong&gt;&lt;a href="https://abrahman2.gumroad.com/l/structured-for-answers" rel="noopener noreferrer"&gt;Structured for Answers&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next in this series:&lt;/strong&gt; &lt;strong&gt;&lt;a href="https://dev.to/arhashir/the-ai-search-audit-8-checks-100-points-one-script-18jp"&gt;The AI Search Audit: 8 Checks, 100 Points, One Script&lt;/a&gt;&lt;/strong&gt; — the full scored audit, including the 40/60 rule that tells you which two categories gate the other six, and one script that runs most of it in about thirty seconds.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>seo</category>
    </item>
  </channel>
</rss>
