<?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: Juan Camilo Auriti</title>
    <description>The latest articles on DEV Community by Juan Camilo Auriti (@juanauriti).</description>
    <link>https://dev.to/juanauriti</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%2F3279930%2F4faa1f38-8049-4b7d-a93f-a217cf7eaefa.jpg</url>
      <title>DEV Community: Juan Camilo Auriti</title>
      <link>https://dev.to/juanauriti</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/juanauriti"/>
    <language>en</language>
    <item>
      <title>My CI hadn't run a single one of those tests in two months and stayed green the whole time</title>
      <dc:creator>Juan Camilo Auriti</dc:creator>
      <pubDate>Wed, 23 Sep 2026 11:00:00 +0000</pubDate>
      <link>https://dev.to/juanauriti/my-ci-hadnt-run-a-single-one-of-those-tests-in-two-months-and-stayed-green-the-whole-time-n15</link>
      <guid>https://dev.to/juanauriti/my-ci-hadnt-run-a-single-one-of-those-tests-in-two-months-and-stayed-green-the-whole-time-n15</guid>
      <description>&lt;p&gt;I added a dependency to a test helper and forgot to declare it in the dev extra. Locally it was already installed, so everything passed. In CI it wasn't, and the tests that needed it did not fail.&lt;/p&gt;

&lt;p&gt;They skipped.&lt;/p&gt;

&lt;p&gt;Skips exit 0. The checkmark stayed green for two months.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it skips instead of failing
&lt;/h2&gt;

&lt;p&gt;The pattern is one line, it's in every codebase, and it's usually correct:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;aiosqlite&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;importorskip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aiosqlite&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That line means: &lt;em&gt;this test needs something optional, and if it isn't here, move on&lt;/em&gt;. Which is right when the dependency is genuinely optional — a test for the Postgres backend on a machine with no Postgres shouldn't fail the build.&lt;/p&gt;

&lt;p&gt;The problem is what it looks like from outside. &lt;code&gt;importorskip&lt;/code&gt; is a decision that the test is optional, taken at import time, and the only place it surfaces is a number in a summary line nobody reads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1284 passed, 168 skipped in 42.11s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;168 skipped. Two months earlier it was 6. Nothing in that output tells you which number is the anomaly, and the exit code is 0 either way.&lt;/p&gt;

&lt;p&gt;After declaring the dependency, the same suite reported &lt;strong&gt;1452 passed&lt;/strong&gt;. The 168 hadn't been slowly rotting. They'd stopped running all at once, on a specific commit, and the build had been green through every one of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that stung
&lt;/h2&gt;

&lt;p&gt;I had a suite I trusted. I merged on green. The whole point of the suite was to tell me when I broke something, and for two months it had quietly stopped being able to.&lt;/p&gt;

&lt;p&gt;Nothing was misconfigured. No warning. No deprecation. CI did exactly what it was asked. The failure mode was that the number of tests it ran was never something anyone asserted on.&lt;/p&gt;

&lt;h2&gt;
  
  
  The guard
&lt;/h2&gt;

&lt;p&gt;There is no &lt;code&gt;--min-tests&lt;/code&gt; flag. There is a hook, and it's eight lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# conftest.py
&lt;/span&gt;&lt;span class="n"&gt;MIN_TESTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1400&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;pytest_sessionfinish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exitstatus&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;collected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;testscollected&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;collected&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;MIN_TESTS&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;option&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collectonly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exitstatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;FAIL: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;collected&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; tests collected, floor is &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;MIN_TESTS&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
              &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Did a dependency stop resolving?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three notes on that, all learned by getting it wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;testscollected&lt;/code&gt;, not passed.&lt;/strong&gt; You want to catch tests that vanished, and a skipped test still counts as collected — so this alone would &lt;em&gt;not&lt;/em&gt; have caught my bug. Which brings me to the second guard, below. Use the floor to catch tests that disappear from collection entirely (a broken import in a conftest, a renamed directory, a bad &lt;code&gt;-k&lt;/code&gt;), because that's the failure that turns a 1452-test suite into 3 without a word.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Guard &lt;code&gt;collectonly&lt;/code&gt;.&lt;/strong&gt; Without it, &lt;code&gt;pytest --collect-only&lt;/code&gt; in a subset trips your own floor and you'll waste twenty minutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Set the floor slightly below current, and raise it.&lt;/strong&gt; Not at current — every new branch that hasn't added a test yet will fail. Below, and bump it when it drifts far.&lt;/p&gt;

&lt;p&gt;For the skip case specifically, the guard is different and simpler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# pytest.ini
&lt;/span&gt;&lt;span class="nn"&gt;[pytest]&lt;/span&gt;
&lt;span class="py"&gt;addopts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;-rs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;-rs&lt;/code&gt; prints the reason for every skip. It doesn't fail anything — it just puts &lt;code&gt;SKIPPED [168] tests/conftest.py:14: could not import 'aiosqlite'&lt;/code&gt; in the log where a human can see it. Two months of my logs had that line. I never read them, because they were green.&lt;/p&gt;

&lt;p&gt;If you want it to actually fail, the strict version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;pytest_sessionfinish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exitstatus&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;skipped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pluginmanager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_plugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;terminalreporter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;skipped&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;skipped&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;MAX_SKIPS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exitstatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I don't use that one. It fights with legitimately environment-dependent tests and I got tired of tuning &lt;code&gt;MAX_SKIPS&lt;/code&gt;. The floor plus &lt;code&gt;-rs&lt;/code&gt; was enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real fix was upstream of all of it
&lt;/h2&gt;

&lt;p&gt;The guards catch the symptom. The cause was that a dependency my tests could not run without was declared as though they could.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# before — aiosqlite nowhere, importorskip papers over it&lt;/span&gt;
&lt;span class="nn"&gt;[project.optional-dependencies]&lt;/span&gt;
&lt;span class="py"&gt;dev&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"pytest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"pytest-cov"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ruff"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c"&gt;# after&lt;/span&gt;
&lt;span class="py"&gt;dev&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"pytest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"pytest-cov"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ruff"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"aiosqlite"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then delete the &lt;code&gt;importorskip&lt;/code&gt; for it. If the suite requires it, &lt;code&gt;importorskip&lt;/code&gt; is a lie that makes the requirement look like a preference.&lt;/p&gt;

&lt;p&gt;That's the rule I took away: &lt;strong&gt;&lt;code&gt;importorskip&lt;/code&gt; is for dependencies you have decided the test can run without. If you haven't made that decision deliberately, you've made it accidentally.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Check yours in one command
&lt;/h2&gt;

&lt;p&gt;If you have a CI suite you trust, this tells you the number you've never looked at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pytest &lt;span class="nt"&gt;--collect-only&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; 2&amp;gt;&amp;amp;1 | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compare it to what you'd have guessed. Then run the real suite with &lt;code&gt;-rs&lt;/code&gt; and read the skip reasons — all of them, once. Mine had a two-month-old import error sitting in plain text under a green checkmark.&lt;/p&gt;

&lt;p&gt;The uncomfortable general version: every green build asserts that the tests that ran, passed. None of them assert that the tests ran.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>python</category>
      <category>devops</category>
      <category>webdev</category>
    </item>
    <item>
      <title>example.com and example.com/ were two rows, and 86.9% of my database was one bug</title>
      <dc:creator>Juan Camilo Auriti</dc:creator>
      <pubDate>Tue, 22 Sep 2026 11:00:00 +0000</pubDate>
      <link>https://dev.to/juanauriti/examplecom-and-examplecom-were-two-rows-and-869-of-my-database-was-one-bug-2p8h</link>
      <guid>https://dev.to/juanauriti/examplecom-and-examplecom-were-two-rows-and-869-of-my-database-was-one-bug-2p8h</guid>
      <description>&lt;p&gt;A scheduled job re-audited domains on an interval. Cheap, idempotent, ran for weeks without complaint.&lt;/p&gt;

&lt;p&gt;Then I looked at row counts. One domain had 1831 audit rows. It should have had a few dozen.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug
&lt;/h2&gt;

&lt;p&gt;Two rows in the domains table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;example.com
example.com/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A unique index on the URL column. Both rows satisfy it — they're different strings. The database was doing exactly what I asked.&lt;/p&gt;

&lt;p&gt;The job then did roughly this: take a domain, run the audit, write the result keyed by the URL the audit resolved to. The audit followed redirects and normalized. So it &lt;strong&gt;read&lt;/strong&gt; &lt;code&gt;example.com&lt;/code&gt; and &lt;strong&gt;wrote&lt;/strong&gt; &lt;code&gt;example.com/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next tick: &lt;code&gt;example.com&lt;/code&gt; still has no recent result — the result went to the other row. Audit it again. Write to the slashed row again.&lt;/p&gt;

&lt;p&gt;Nothing errored. Every individual run was correct. The loop simply never converged, because the key it read by and the key it wrote by were never the same key.&lt;/p&gt;

&lt;p&gt;Across the table, rows attributable to this were &lt;strong&gt;86.9% of all audit rows&lt;/strong&gt;. Not 86.9% of one domain. Of the table.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it stayed invisible
&lt;/h2&gt;

&lt;p&gt;Every signal I had was pointed the wrong way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No errors.&lt;/strong&gt; Each audit succeeded. Logs were clean.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The unique index looked like protection.&lt;/strong&gt; I had convinced myself duplicates were structurally impossible, so when I saw two similar rows my first thought was that I was misreading the query.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost looked like growth.&lt;/strong&gt; Audit volume climbing looked like the product being used. It was the same handful of domains being re-audited forever. That one stings — I had a metric moving in the right direction for entirely the wrong reason, and I felt good about it for weeks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Per-domain views looked fine.&lt;/strong&gt; Open one domain, see a sane history. The pathology only appears when you group by normalized key and compare against raw key, which is not a query anyone writes by accident.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;Normalize at the boundary, once, before anything touches storage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.parse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlsplit&lt;/span&gt;

&lt;span class="n"&gt;SCHEME_RE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;^[a-z][a-z0-9+.\-]*://&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;SCHEME_RE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;urlsplit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hostname&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;www.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no host in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rstrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scheme&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;://&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two load-bearing details, and I got one of them wrong the first time I wrote this out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;rstrip("/") or "/"&lt;/code&gt;.&lt;/strong&gt; Strip the trailing slash, but if that leaves an empty path, put one back — so the root is always exactly &lt;code&gt;/&lt;/code&gt; and never the empty string. Without the &lt;code&gt;or "/"&lt;/code&gt; you've just invented a third spelling of the same page and replaced the bug with a subtler one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The scheme has to go on before &lt;code&gt;urlsplit&lt;/code&gt;, not after.&lt;/strong&gt; &lt;code&gt;urlsplit("example.com")&lt;/code&gt; does not give you a host. With no &lt;code&gt;//&lt;/code&gt;, it reads the whole string as a &lt;em&gt;path&lt;/em&gt; — &lt;code&gt;hostname&lt;/code&gt; comes back &lt;code&gt;None&lt;/code&gt;. My first version handled that with &lt;code&gt;p.hostname or ""&lt;/code&gt;, which silently produced a URL with no host in it at all, for the single most common input a user types by hand. The &lt;code&gt;SCHEME_RE&lt;/code&gt; prepend fixes it; the &lt;code&gt;raise&lt;/code&gt; makes sure that if a host is still missing, I hear about it instead of storing something shaped like a URL.&lt;/p&gt;

&lt;p&gt;That second one is the same class of bug as the story above: a value that's wrong but well-formed enough to store, so nothing complains.&lt;/p&gt;

&lt;p&gt;Two rules that matter more than the function:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One normalizer, called at the boundary.&lt;/strong&gt; Not in the job, not in the API handler, not in the audit. In the one place a URL becomes a record. Every additional call site is a chance for two of them to disagree, which is the bug again wearing a different hat.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Normalize before the uniqueness check, not after.&lt;/strong&gt; A unique index only protects the shape you hand it. Mine was enforcing uniqueness on a key I hadn't canonicalized, which is enforcement theater.&lt;/p&gt;

&lt;p&gt;Then backfill: normalize existing rows, merge collisions, keep the earliest &lt;code&gt;created_at&lt;/code&gt; and the most recent result. The 1831-row domain came out at 19.&lt;/p&gt;

&lt;h2&gt;
  
  
  Careful with &lt;code&gt;www&lt;/code&gt; and &lt;code&gt;rstrip&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Two things in that function are opinions, not facts, and you should hold them deliberately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dropping &lt;code&gt;www&lt;/code&gt;&lt;/strong&gt; treats &lt;code&gt;www.example.com&lt;/code&gt; and &lt;code&gt;example.com&lt;/code&gt; as the same entity. Almost always what you want, because almost every site redirects one to the other. Not universally true — if a host serves genuinely different content on the two, this merges two things that aren't one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stripping the trailing path slash&lt;/strong&gt; treats &lt;code&gt;/about&lt;/code&gt; and &lt;code&gt;/about/&lt;/code&gt; as the same page. Also almost always right, also not guaranteed by HTTP. Two different resources at those two paths is legal and rude, and it exists.&lt;/p&gt;

&lt;p&gt;I took both trade-offs on purpose. The cost of merging two things that were actually one is a slightly wrong row. The cost of &lt;em&gt;not&lt;/em&gt; merging is what I just described.&lt;/p&gt;

&lt;h2&gt;
  
  
  The transferable part
&lt;/h2&gt;

&lt;p&gt;The trailing slash isn't the lesson. The lesson is the shape: &lt;strong&gt;a loop that reads by one key and writes by another will never terminate, and will never raise.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you have a scheduled job that's supposed to converge, the question to ask isn't "is it erroring." It's: &lt;em&gt;is the key I select by byte-identical to the key I write by?&lt;/em&gt; If a normalizer, a redirect, or a &lt;code&gt;.lower()&lt;/code&gt; sits between the read and the write, the answer is no, and the job will run forever while every individual execution looks correct.&lt;/p&gt;

&lt;p&gt;Cheapest check available — group by normalized key, count distinct raw keys:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rtrim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;spellings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;domains&lt;/span&gt; &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;HAVING&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that returns anything, you have the bug. It took me weeks to think of running it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Tomorrow, outside this series: my CI hadn't run a chunk of my test suite in two months, and the checkmark was green the whole time.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>database</category>
      <category>postgres</category>
      <category>debugging</category>
    </item>
    <item>
      <title>Your JSON-LD is probably inside a @graph, and most parsers don't look there</title>
      <dc:creator>Juan Camilo Auriti</dc:creator>
      <pubDate>Mon, 21 Sep 2026 11:00:00 +0000</pubDate>
      <link>https://dev.to/juanauriti/your-json-ld-is-probably-inside-a-graph-and-most-parsers-dont-look-there-3njj</link>
      <guid>https://dev.to/juanauriti/your-json-ld-is-probably-inside-a-graph-and-most-parsers-dont-look-there-3njj</guid>
      <description>&lt;p&gt;There are two shapes a page can hand you its structured data in, and if you only handle the first one you will report that a correctly marked-up page has no structured data at all.&lt;/p&gt;

&lt;p&gt;Shape one, the one every tutorial shows:&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;"Article"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"headline"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;Shape two, the one a large share of the real web actually emits:&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;"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;"..."&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;"WebSite"&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;"..."&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;"..."&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;"Article"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"headline"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;Shape two has no top-level &lt;code&gt;@type&lt;/code&gt;. A check written as &lt;code&gt;data['@type'] == 'Article'&lt;/code&gt; returns false. Not "malformed" — false. The page looks empty.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who emits shape two
&lt;/h2&gt;

&lt;p&gt;Yoast SEO and RankMath both do, by default, and they don't offer a flat-output mode. Between them that's a very large fraction of every WordPress site with an SEO plugin, which is a very large fraction of the web.&lt;/p&gt;

&lt;p&gt;And it's the better shape. The &lt;code&gt;@graph&lt;/code&gt; form lets nodes reference each other by &lt;code&gt;@id&lt;/code&gt; — the &lt;code&gt;Article&lt;/code&gt; points at the &lt;code&gt;WebPage&lt;/code&gt; it lives on, which points at the &lt;code&gt;WebSite&lt;/code&gt;, which points at the publishing &lt;code&gt;Organization&lt;/code&gt;. One entity graph instead of four disconnected islands repeating the same publisher name. Both plugins are right to emit it.&lt;/p&gt;

&lt;p&gt;Which makes a parser that can't read it entirely the parser's problem.&lt;/p&gt;

&lt;p&gt;It also makes shape one — the shape in every tutorial — the shape almost nobody's CMS actually produces. Including in my own writing: I've handed out &lt;code&gt;Organization + WebSite&lt;/code&gt; JSON-LD templates twice on this site, in &lt;a href="https://dev.to/juanauriti/how-to-make-your-site-quotable-by-ai-in-30-minutes-174f"&gt;How to Make Your Site Quotable by AI in 30 Minutes&lt;/a&gt; and in &lt;a href="https://dev.to/juanauriti/the-4-layer-model-for-ai-search-readiness-what-i-learned-auditing-360-sites-h17"&gt;The 4-Layer Model for AI Search Readiness&lt;/a&gt;, and both are flat. They're correct — a flat block is valid, and if you're adding JSON-LD by hand to a page that has none, it's the right thing to write.&lt;/p&gt;

&lt;p&gt;They just aren't what you'll find when you go &lt;em&gt;read&lt;/em&gt; a page that already has markup. If you followed either of those posts on a WordPress site with Yoast installed, your plugin was already emitting a &lt;code&gt;@graph&lt;/code&gt; and your hand-written block landed next to it, which is a different situation than the one I described. That's on me, and it's the gap this post exists to close.&lt;/p&gt;

&lt;h2&gt;
  
  
  How many parsers get it wrong
&lt;/h2&gt;

&lt;p&gt;I tested twelve structured-data detectors — validators, audit tools, citability checkers, some open source and some hosted — against the same page, once with flat JSON-LD and once with the same types wrapped in a &lt;code&gt;@graph&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Nine of the twelve reported no structured data on the &lt;code&gt;@graph&lt;/code&gt; version.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Including mine.&lt;/strong&gt; My own tool had this bug, which is the only reason I went looking. A page I knew had good markup was scoring zero on schema, and my first assumption was that the page was broken.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix is boring
&lt;/h2&gt;

&lt;p&gt;Flatten before you inspect. Once, at the parse boundary, so no downstream check has to know about either shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;iter_nodes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Yield every schema.org node, flat or @graph, at any nesting depth.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nf"&gt;iter_nodes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;@graph&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nf"&gt;iter_nodes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;@graph&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;@type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things worth pointing out.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;data&lt;/code&gt; can be a &lt;strong&gt;list&lt;/strong&gt; at the top level. A page is allowed to ship several &lt;code&gt;&amp;lt;script type="application/ld+json"&amp;gt;&lt;/code&gt; blocks, and some CMSes put an array in one block. Handle it or you'll drop everything after the first.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;@graph&lt;/code&gt; check runs &lt;strong&gt;before&lt;/strong&gt; the &lt;code&gt;@type&lt;/code&gt; check, and both run — not &lt;code&gt;elif&lt;/code&gt;. A node can legitimately carry its own &lt;code&gt;@type&lt;/code&gt; &lt;em&gt;and&lt;/em&gt; nest a &lt;code&gt;@graph&lt;/code&gt;. Using &lt;code&gt;elif&lt;/code&gt; there is how you silently drop half a graph.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recursion, not one level of unwrapping.&lt;/strong&gt; I have seen &lt;code&gt;@graph&lt;/code&gt; inside &lt;code&gt;@graph&lt;/code&gt;. Rare, but it costs nothing to handle.&lt;/p&gt;

&lt;p&gt;Then every check downstream stops caring:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;types&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;@type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;iter_nodes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;@type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;isinstance&lt;/code&gt; guard is there because &lt;code&gt;@type&lt;/code&gt; is allowed to be an array — &lt;code&gt;"@type": ["Person", "Organization"]&lt;/code&gt; is valid, and a bare set comprehension will throw an unhashable-type error on it the first time a real page hands you one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check your own page in ten seconds
&lt;/h2&gt;

&lt;p&gt;Paste into the console on any page you own:&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="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="nf"&gt;querySelectorAll&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[type="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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;parse&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;textContent&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@graph&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@graph, &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@graph&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; nodes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;flat, &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it prints &lt;code&gt;@graph&lt;/code&gt;, then any tool that told you your structured data was missing was telling you about itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I care about this beyond my own bug
&lt;/h2&gt;

&lt;p&gt;The reason a parser bug is worth a thousand words: the check that's wrong is usually the one you trust to tell you something is wrong.&lt;/p&gt;

&lt;p&gt;I spent a while assuming a page was badly marked up because a tool said so. The tool was the broken thing. That failure mode is much worse than a false negative on a real problem, because it points you at the wrong file and you fix something that was never broken.&lt;/p&gt;

&lt;p&gt;So when an audit tool tells you a page has no structured data, and you can see the JSON-LD in the source with your own eyes, believe your eyes and check the shape first.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Next in this series: two URLs that differed by one trailing character, and the re-audit loop that ate 86.9% of my database.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>seo</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>ai</category>
    </item>
    <item>
      <title>A prompt told me my tool was built on a product that doesn't exist</title>
      <dc:creator>Juan Camilo Auriti</dc:creator>
      <pubDate>Fri, 18 Sep 2026 11:00:00 +0000</pubDate>
      <link>https://dev.to/juanauriti/a-prompt-told-me-my-tool-was-built-on-a-product-that-doesnt-exist-58np</link>
      <guid>https://dev.to/juanauriti/a-prompt-told-me-my-tool-was-built-on-a-product-that-doesnt-exist-58np</guid>
      <description>&lt;p&gt;I ran a comparison prompt against Gemini to see how it described the CLI I maintain. It came back with this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;a "Local SEO &amp;amp; AI Indexing" tool, built on top of Scrapeless&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Two claims, both wrong. It isn't a local SEO tool, and there is no Scrapeless anywhere underneath it — no dependency, no API call, no mention in the repo. The model hadn't hallucinated freely, though. It had resolved an ambiguity, and picked the wrong branch.&lt;/p&gt;

&lt;p&gt;I've written before that &lt;a href="https://dev.to/juanauriti/why-your-brand-is-invisible-to-ai-search-its-not-about-rankings-56pd"&gt;entity clarity is one of the things AI search actually optimizes for&lt;/a&gt;. That post made the case in the abstract. This is the same problem arriving on my own doorstep with a vendor name attached, which turned out to be considerably more instructive.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ambiguity
&lt;/h2&gt;

&lt;p&gt;The tool does GEO — Generative Engine Optimization. Getting pages cited by AI assistants.&lt;/p&gt;

&lt;p&gt;GEO is also, to most of the indexed web, short for &lt;em&gt;geographic&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I went looking for how the acronym actually appears in the corpus a model would have read, and the collision is everywhere. Repositories describe themselves with phrases like &lt;em&gt;"SERP monitoring and GEO tracking"&lt;/em&gt; and &lt;em&gt;"geographic research and AI indexing"&lt;/em&gt; — using GEO and geographic interchangeably, in the same sentence as SEO and AI. A model reading a bare &lt;code&gt;GEO&lt;/code&gt; with no disambiguating context nearby has two live readings, and the geographic one has vastly more mass behind it.&lt;/p&gt;

&lt;p&gt;Once it picks that branch, it needs a vendor that fits. So it attaches whichever product occupies the local-SEO-plus-scraping space. That's where Scrapeless came from. The model didn't invent a dependency; it filled a slot the wrong reading had opened.&lt;/p&gt;

&lt;h2&gt;
  
  
  The counter-evidence that told me it was fixable
&lt;/h2&gt;

&lt;p&gt;Here's the part that turned this from "models are unreliable" into an actionable bug.&lt;/p&gt;

&lt;p&gt;Prompts that already carried context — anything containing &lt;em&gt;open-source&lt;/em&gt;, &lt;em&gt;CLI&lt;/em&gt;, &lt;em&gt;Python&lt;/em&gt; — described the product correctly. Same model, same product, same week. The difference was entirely in whether the surrounding tokens ruled the geographic reading out before resolution happened.&lt;/p&gt;

&lt;p&gt;That reframes the problem. The model wasn't refusing to understand. It was resolving an ambiguity I had left open, and it resolved it against me whenever nothing nearby closed it.&lt;/p&gt;

&lt;p&gt;Which means the fix isn't more content. It's &lt;strong&gt;adjacency&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;I put the expansion next to the name in the places a model reads first, and nowhere else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The hero paragraph.&lt;/strong&gt; It now states what the thing is in a full sentence, in the first block of body text. This had a second benefit I hadn't planned: an entity-disambiguation check wants a definition sentence — subject, copula, category — somewhere in the content. The page didn't have one. Now it does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The meta description.&lt;/strong&gt; This one cost something. The description was already at the limit, so fitting the expansion meant dropping the list of supported engines. 152 characters now, under the 160 the tests enforce. I traded a feature list for a definition, deliberately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The JSON-LD graph.&lt;/strong&gt; &lt;code&gt;Organization&lt;/code&gt; already carried the full name. &lt;code&gt;WebSite&lt;/code&gt; and &lt;code&gt;WebApplication&lt;/code&gt; didn't — they had the bare brand. A model walking the graph would hit two nodes that reintroduced the exact ambiguity the hero had just resolved. Both now carry it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I deliberately didn't touch
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The titles.&lt;/strong&gt; They sit at 57 characters against a 60-character cap. There is no room to add nineteen characters, and pushing the brand later in the title to make space costs more in the search result than the disambiguation gains. Titles stayed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The README, the package description, llms.txt.&lt;/strong&gt; All three already spelled it out. That's part of why this was confusing at first — the expansion existed in plenty of places. It just wasn't in the places that get read &lt;em&gt;first&lt;/em&gt;, next to the name, on the page a model lands on.&lt;/p&gt;

&lt;p&gt;That distinction is the whole lesson. Coverage isn't the metric. Position relative to the entity is.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it measured
&lt;/h2&gt;

&lt;p&gt;Measured on the built HTML, before and after:&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;before&lt;/th&gt;
&lt;th&gt;after&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;entity disambiguation&lt;/td&gt;
&lt;td&gt;1/3&lt;/td&gt;
&lt;td&gt;3/3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;citability score&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;62&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two points. I want to be honest about how small that is. The citability score moved barely at all, because a definition sentence is one signal among many and the page was already reasonable on most of them.&lt;/p&gt;

&lt;p&gt;The score isn't the point. The point is that the specific failure — a model confidently naming a dependency that does not exist — has a specific, cheap cause, and the cause is an ambiguity you can close in three files.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you have an acronym
&lt;/h2&gt;

&lt;p&gt;Check whether it collides. The test is one prompt: ask a model to describe your product without giving it any context beyond the name, and see which sense it resolves to. If it picks wrong, you don't have a model problem, you have an adjacency problem.&lt;/p&gt;

&lt;p&gt;Then check the four places, in this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;First body paragraph&lt;/strong&gt; — a real definition sentence, not a tagline. Subject, is, category.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Meta description&lt;/strong&gt; — the expansion beats the feature list. It's worth the trade.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every node in your JSON-LD&lt;/strong&gt; — not just &lt;code&gt;Organization&lt;/code&gt;. A &lt;code&gt;WebSite&lt;/code&gt; with a bare brand name undoes the paragraph above it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Titles&lt;/strong&gt; — only if you have room under the cap. Usually you don't, and that's fine.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One thing I can't tell you yet: whether the fix propagated. Models don't re-read on your schedule, and I have no way to attribute a corrected description to a specific commit. I'll re-run the same prompt in a month and report the result either way, including if nothing changed.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Next in this series: the JSON-LD your CMS actually emits, and why most parsers — mine included, until I fixed it — don't look in the right place.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>seo</category>
      <category>ai</category>
      <category>webdev</category>
      <category>writing</category>
    </item>
    <item>
      <title>I almost started a Medium column about getting cited by LLMs. Then I read Medium's robots.txt</title>
      <dc:creator>Juan Camilo Auriti</dc:creator>
      <pubDate>Thu, 17 Sep 2026 07:48:21 +0000</pubDate>
      <link>https://dev.to/juanauriti/i-almost-started-a-medium-column-about-getting-cited-by-llms-then-i-read-mediums-robotstxt-1ak3</link>
      <guid>https://dev.to/juanauriti/i-almost-started-a-medium-column-about-getting-cited-by-llms-then-i-read-mediums-robotstxt-1ak3</guid>
      <description>&lt;p&gt;I was about to start a monthly column about AI search visibility — how to get your pages cited by ChatGPT, Perplexity, Google's AI Mode. Medium was the obvious venue. Big audience, publications that distribute for you, no infrastructure to run.&lt;/p&gt;

&lt;p&gt;Before writing the first paragraph I ran the check I run on every client site:&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://medium.com/robots.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Medium disallows the crawlers I was writing about.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's actually in the file
&lt;/h2&gt;

&lt;p&gt;Checked 2026-09-17, HTTP 200. Alongside the usual &lt;code&gt;*&lt;/code&gt; rules there's a second group:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight robot_framework"&gt;&lt;code&gt;User-Agent: Amazonbot&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;User-Agent:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Applebot-Extended&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;User-Agent:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Bytespider&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;User-Agent:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ClaudeBot&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;User-Agent:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;FacebookBot&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;User-Agent:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;GoogleOther&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;User-Agent:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;GPTBot&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;User-Agent:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;meta-externalagent&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Disallow:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Allow:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/about&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Allow:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/business&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Allow:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/earn&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Allow:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/gift&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Allow:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/membership&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Allow:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/partner-program&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Allow:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/verified-authors&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eight user-agents, &lt;code&gt;Disallow: /&lt;/code&gt;, and an allow-list containing exactly the pages that sell Medium. Your post is not on that list.&lt;/p&gt;

&lt;p&gt;That's a defensible business decision — Medium licenses its archive and doesn't want it taken for free. It's just the opposite of what I needed from the platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Blocked isn't the same as invisible
&lt;/h2&gt;

&lt;p&gt;This is where most takes on this get sloppy, so it's worth being precise. "AI crawler" covers two jobs, and Medium only closes one of them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Training and bulk collection — closed.&lt;/strong&gt; &lt;code&gt;GPTBot&lt;/code&gt; (OpenAI), &lt;code&gt;ClaudeBot&lt;/code&gt; (Anthropic), &lt;code&gt;Applebot-Extended&lt;/code&gt; (Apple Intelligence), &lt;code&gt;meta-externalagent&lt;/code&gt; (Meta), &lt;code&gt;Bytespider&lt;/code&gt; (ByteDance), &lt;code&gt;Amazonbot&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Search-grounded retrieval — open.&lt;/strong&gt; These are not in the blocked group, so the &lt;code&gt;*&lt;/code&gt; rules apply and they're allowed:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;user-agent&lt;/th&gt;
&lt;th&gt;what it feeds&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Googlebot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Google's index → AI Overviews, AI Mode&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;Bing's index → Copilot&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's own index&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;ChatGPT's search index&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;user-triggered fetch when ChatGPT opens a link&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;Gemini / Vertex training use of Google-crawled pages&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;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So a Medium post can absolutely turn up as a cited source in Perplexity or an AI Overview. It reaches those engines through a search index, and those indexes are open.&lt;/p&gt;

&lt;p&gt;Two of those rows deserve a second look.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Google-Extended&lt;/code&gt; isn't blocked. It doesn't crawl anything itself — it's the token that controls whether content Google already crawled may be used for Gemini training. Left unblocked, that path stays open while OpenAI's and Anthropic's equivalents are shut.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CCBot&lt;/code&gt; isn't blocked either. Common Crawl is a public archive that a lot of training corpora are built from. Blocking the labs' own crawlers while leaving the archive they can download open is a narrower measure than it first looks.&lt;/p&gt;

&lt;p&gt;I don't read this as a loophole anyone left on purpose. I read it as the normal state of a robots.txt: a list of names, maintained by hand, always slightly behind a user-agent landscape that adds a new one every few months.&lt;/p&gt;

&lt;h2&gt;
  
  
  Six platforms, same check
&lt;/h2&gt;

&lt;p&gt;Same command, same day:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;platform&lt;/th&gt;
&lt;th&gt;AI crawler rules in the &lt;code&gt;*&lt;/code&gt; group&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;medium.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;separate group blocking 8 AI user-agents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dev.to&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;none — only &lt;code&gt;/*/actions_panel*&lt;/code&gt; disallowed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hashnode.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;none — &lt;code&gt;Allow: /&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;substack.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;none — only &lt;code&gt;/action/&lt;/code&gt; disallowed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;news.ycombinator.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;none — open, &lt;code&gt;Crawl-delay: 30&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reddit.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;User-agent: *&lt;/code&gt; + &lt;code&gt;Disallow: /&lt;/code&gt; — everything blocked&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Reddit is the interesting one. It doesn't single out AI crawlers; it disallows all of them, then licenses the archive to Google and OpenAI directly. Reddit threads do reach those two models, but through a contract, not through a crawl. If you were counting a Reddit comment as third-party evidence any model could go verify, that's not what it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I cared enough to check
&lt;/h2&gt;

&lt;p&gt;A model reading your own domain treats what it finds as a self-description. The same sentence on a domain you don't control reads differently — and that difference is the entire reason to publish somewhere other than your own site.&lt;/p&gt;

&lt;p&gt;I have a concrete case. A comparison prompt to Gemini described my tool as a &lt;em&gt;"Local SEO &amp;amp; AI Indexing"&lt;/em&gt; product &lt;em&gt;"built on top of Scrapeless."&lt;/em&gt; There is no such product underneath it. The acronym did the damage: a lot of indexed repositories use GEO and "geographic" interchangeably, so a model reading a bare GEO with no nearby context can resolve it to the geographic sense and attach whatever vendor sits in that space.&lt;/p&gt;

&lt;p&gt;The fix was boring. Put the expansion next to the name where a model reads first — hero paragraph, meta description, the &lt;code&gt;WebSite&lt;/code&gt; and &lt;code&gt;WebApplication&lt;/code&gt; nodes in the JSON-LD graph. On the built HTML, the entity-disambiguation check went from 1/3 to 3/3 and the citability score from 60 to 62.&lt;/p&gt;

&lt;p&gt;But that fix only works on pages I own. The correction that matters more is the one on a domain that isn't mine — which is why the platform's robots.txt is a prerequisite, not a detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule
&lt;/h2&gt;

&lt;p&gt;Before you commit a column to a platform, read the platform's robots.txt. It's one command, and it tells you which of the two channels you're buying.&lt;/p&gt;

&lt;p&gt;Then be honest about which you needed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Human readers and distribution&lt;/strong&gt; → Medium is fine. It has the audience and the publications.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Corroboration a model can crawl&lt;/strong&gt; → pick a platform that doesn't disallow the crawlers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wanted the second, so this column is on dev.to.&lt;/p&gt;

&lt;p&gt;Three caveats worth stating plainly. &lt;code&gt;robots.txt&lt;/code&gt; is a request, not enforcement — it tells you a platform's stated policy, not what every crawler does. The lists change; mine is a reading from one day, and yours should be your own. And a publication on a custom subdomain can serve a different file than its parent, so check the host you'll actually publish under.&lt;/p&gt;

&lt;p&gt;If you want the same check across the AI user-agents on your own domain, that's the first thing &lt;a href="https://geoready.dev" rel="noopener noreferrer"&gt;the CLI I maintain&lt;/a&gt; does — &lt;code&gt;geo access&lt;/code&gt; against your robots.txt. But you don't need it for this. You need &lt;code&gt;curl&lt;/code&gt; and two minutes, on the platform you were about to hand your writing to.&lt;/p&gt;

</description>
      <category>seo</category>
      <category>ai</category>
      <category>webdev</category>
      <category>writing</category>
    </item>
    <item>
      <title>How to Make Your Site Quotable by AI in 30 Minutes</title>
      <dc:creator>Juan Camilo Auriti</dc:creator>
      <pubDate>Wed, 12 Aug 2026 12:59:55 +0000</pubDate>
      <link>https://dev.to/juanauriti/how-to-make-your-site-quotable-by-ai-in-30-minutes-174f</link>
      <guid>https://dev.to/juanauriti/how-to-make-your-site-quotable-by-ai-in-30-minutes-174f</guid>
      <description>&lt;p&gt;A practical, copy-paste guide to the four infrastructure fixes that determine whether ChatGPT, Perplexity, and Google AI Overviews cite your site or skip it.&lt;/p&gt;

&lt;p&gt;I audited 360 domains against an AI-search-readiness framework. The average score was 54.1 out of 100. Most sites fail not because of content quality, but because of four infrastructure issues that take 30 minutes to fix.&lt;/p&gt;

&lt;p&gt;This is the quick version. Four steps, real code, in order.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why 30 Minutes
&lt;/h2&gt;

&lt;p&gt;AI search is a selection problem, not a ranking problem. A model receives a query, decides which sources to consult, extracts a passage, and either cites you or paraphrases without attribution. There is no page two.&lt;/p&gt;

&lt;p&gt;Getting selected requires four things to work in sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Access&lt;/strong&gt; — the AI crawler can reach your content&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Orientation&lt;/strong&gt; — it can find what matters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Understanding&lt;/strong&gt; — it can parse what you are&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quotability&lt;/strong&gt; — it can extract a self-contained passage&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Fix access before schema. Fix schema before content. Wrong order = wasted work. I saw teams rewriting content for "AI optimization" while their &lt;code&gt;robots.txt&lt;/code&gt; blocked GPTBot.&lt;/p&gt;

&lt;p&gt;Let's go.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: robots.txt (5 minutes)
&lt;/h2&gt;

&lt;p&gt;There are at least 11 AI crawlers actively indexing the web. Each has a distinct user-agent token. Most sites block them by accident — a CMS, a security plugin, or a boilerplate template added &lt;code&gt;Disallow: /&lt;/code&gt; and nobody reviewed it.&lt;/p&gt;

&lt;p&gt;Check what you're serving right now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://yoursite.com/robots.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you see &lt;code&gt;Disallow: /&lt;/code&gt; under &lt;code&gt;User-agent: *&lt;/code&gt;, you're blocking everything. If you see &lt;code&gt;Disallow: /&lt;/code&gt; with no user-agent specified, same thing.&lt;/p&gt;

&lt;p&gt;Here's a minimal &lt;code&gt;robots.txt&lt;/code&gt; that explicitly allows the major AI crawlers while keeping private paths locked down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Allow major AI crawlers access to public content
User-agent: GPTBot
Allow: /

User-agent: OAI-SearchBot
Allow: /

User-agent: PerplexityBot
Allow: /

User-agent: ClaudeBot
Allow: /

User-agent: Claude-SearchBot
Allow: /

User-agent: Googlebot
Allow: /

User-agent: Google-Extended
Allow: /

User-agent: Applebot
Allow: /

User-agent: CCBot
Allow: /

User-agent: Bytespider
Allow: /

User-agent: Diffbot
Allow: /

# Block private/admin paths from all crawlers
User-agent: *
Disallow: /admin/
Disallow: /private/
Disallow: /api/internal/

# Sitemap
Sitemap: https://yoursite.com/sitemap.xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Upload this to your site root. Done.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Rendering Trap
&lt;/h3&gt;

&lt;p&gt;One more thing on access: if your site is a SPA that returns an empty &lt;code&gt;&amp;lt;div id="root"&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt; on the initial HTML response, AI crawlers see nothing. GPTBot and PerplexityBot do not execute JavaScript reliably.&lt;/p&gt;

&lt;p&gt;Check what a crawler actually sees:&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;-A&lt;/span&gt; &lt;span class="s2"&gt;"GPTBot"&lt;/span&gt; https://yoursite.com | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"your main heading"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that returns nothing, your content is invisible. You need SSR, SSG, or a prerendering layer. This is not a 5-minute fix, but you need to know about it now.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: llms.txt (5 minutes)
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;llms.txt&lt;/code&gt; is a plain-text file at the root of your site that gives AI crawlers a structured summary of your content. Think of it as a sitemap that a model can actually read.&lt;/p&gt;

&lt;p&gt;In my audit, 54.2% of sites had a &lt;code&gt;llms.txt&lt;/code&gt; file, but only 26.9% had a complete one. A broken &lt;code&gt;llms.txt&lt;/code&gt; is worse than none — it sends a model a map with missing streets.&lt;/p&gt;

&lt;p&gt;Here's a minimal template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Your Company Name

&amp;gt; Your Company builds [what you build] for [who you serve].

## Docs
- [Getting Started](https://yoursite.com/docs/getting-started): Quick start guide
- [API Reference](https://yoursite.com/docs/api): Full REST API documentation

## Product
- [Features](https://yoursite.com/features): Feature overview
- [Pricing](https://yoursite.com/pricing): Pricing tiers and FAQ

## Blog
- [Blog Index](https://yoursite.com/blog): Engineering and product blog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;#&lt;/code&gt; line is your site title&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;&amp;gt;&lt;/code&gt; line is a one-sentence summary of what you do&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;##&lt;/code&gt; sections group links&lt;/li&gt;
&lt;li&gt;Each link is &lt;code&gt;- [Title](URL): Description&lt;/code&gt; — the description matters, it gives the model context about what's at that URL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don't list every blog post. List the pages that answer "what is this site?" and "what does it do?"&lt;/p&gt;

&lt;p&gt;Upload to &lt;code&gt;https://yoursite.com/llms.txt&lt;/code&gt;. Done.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Schema.org JSON-LD (10 minutes)
&lt;/h2&gt;

&lt;p&gt;Schema is how you tell a model what your entities are: who you are, what your site does, what questions your pages answer.&lt;/p&gt;

&lt;p&gt;In the audit, 75.6% of sites had some schema, but only 52.2% had Organization schema and 18.1% had FAQ schema. The gap between "has some schema" and "has the schema types that matter for AI citation" is where most sites lose ground.&lt;/p&gt;

&lt;h3&gt;
  
  
  Organization + WebSite Schema
&lt;/h3&gt;

&lt;p&gt;Drop this in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; of your homepage. Replace the values with your real data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"application/ld+json"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@context&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://schema.org&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@graph&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Organization&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://yoursite.com/#organization&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Your Company&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;url&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://yoursite.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;logo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://yoursite.com/logo.png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Your Company builds [what you build].&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sameAs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://github.com/yourcompany&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://x.com/yourcompany&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://www.linkedin.com/company/yourcompany&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;contactPoint&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ContactPoint&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;contactType&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;support&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;support@yoursite.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;url&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://yoursite.com/contact&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WebSite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://yoursite.com/#website&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;url&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://yoursite.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Your Company&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;What your site does, in one sentence.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;publisher&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://yoursite.com/#organization&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;potentialAction&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SearchAction&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;target&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;EntryPoint&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;urlTemplate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://yoursite.com/search?q={search_term_string}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;query-input&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;required name=search_term_string&lt;/span&gt;&lt;span class="dl"&gt;"&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="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;@graph&lt;/code&gt; structure lets you declare multiple entities in one block and cross-reference them with &lt;code&gt;@id&lt;/code&gt;. This is how you tell a model "this organization publishes this website."&lt;/p&gt;

&lt;h3&gt;
  
  
  FAQ Schema
&lt;/h3&gt;

&lt;p&gt;If you have FAQ pages or pages that answer questions, add this on the relevant page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"application/ld+json"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@context&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://schema.org&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FAQPage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mainEntity&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Question&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;What is AI search readiness?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;acceptedAnswer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Answer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AI search readiness is the degree to which a website can be discovered, understood, and cited by generative AI systems like ChatGPT, Perplexity, and Google AI Overviews.&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Question&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;How is GEO different from SEO?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;acceptedAnswer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Answer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SEO optimizes for ranking positions on a search results page. GEO optimizes for selection and citation by generative models that synthesize answers from multiple sources.&lt;/span&gt;&lt;span class="dl"&gt;"&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="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;FAQ schema is your direct line to question-answer extraction. When a model sees &lt;code&gt;FAQPage&lt;/code&gt; with &lt;code&gt;Question&lt;/code&gt; and &lt;code&gt;acceptedAnswer&lt;/code&gt; pairs, it can pull those answers verbatim. That's why the 18.1% adoption number matters — it's the schema type most directly tied to getting quoted, and 82% of sites don't have it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Content Structure — BLUF (10 minutes)
&lt;/h2&gt;

&lt;p&gt;The final step is editing, not infrastructure. A model can only quote you cleanly if your content is structured to be quoted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BLUF&lt;/strong&gt;: Bottom Line Up Front. Every page should open with a standalone answer to its core question. Details go below. The answer goes first.&lt;/p&gt;

&lt;h3&gt;
  
  
  Before: Narrative Structure (Hard to Quote)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# How Our API Handles Rate Limiting&lt;/span&gt;

When we first launched our API in 2023, we didn't have any rate limiting
in place. After a few incidents where a single client overwhelmed the
auth service, we realized we needed a more robust approach. We
experimented with token bucket algorithms, considered sliding window
loggers, and eventually settled on a fixed window counter approach
combined with exponential backoff. Here's how it works...

[800 more words of context, history, and implementation details]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A model reading this has to synthesize the answer from scattered sentences. It will paraphrase without citing, or skip this source for one that's cleaner.&lt;/p&gt;

&lt;h3&gt;
  
  
  After: BLUF Structure (Easy to Quote)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# How Our API Handles Rate Limiting&lt;/span&gt;

Our API enforces rate limiting using a &lt;span class="gs"&gt;**fixed window counter**&lt;/span&gt; with
exponential backoff. The default limit is &lt;span class="gs"&gt;**100 requests per minute**&lt;/span&gt;
per API key. When the limit is exceeded, the API returns HTTP 429 with
a &lt;span class="sb"&gt;`Retry-After`&lt;/span&gt; header indicating the wait time in seconds.

&lt;span class="gu"&gt;## How It Works&lt;/span&gt;

Rate limits are calculated per API key, not per IP address. Each
request increments a counter that resets at the start of each
60-second window. When the counter exceeds 100, subsequent requests
receive a 429 response until the window resets.

&lt;span class="gu"&gt;## Handling 429 Responses&lt;/span&gt;

Clients should implement exponential backoff: wait 1 second before
the first retry, then double the wait on each subsequent retry, up to
a maximum of 60 seconds. The &lt;span class="sb"&gt;`Retry-After`&lt;/span&gt; header provides the exact
wait time for the current window.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first paragraph is self-contained. A model can extract it as a direct quote. It makes sense outside the context of the full page. That's quotability.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Editing Rule
&lt;/h3&gt;

&lt;p&gt;Go to your top 5 most important pages. For each one:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Find the core question the page answers&lt;/li&gt;
&lt;li&gt;Write a 2-3 sentence answer that stands alone&lt;/li&gt;
&lt;li&gt;Put it at the top, before any context or history&lt;/li&gt;
&lt;li&gt;Move the details below&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is the step that takes the most effort but has the highest impact on whether you get quoted or paraphrased.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Order Matters
&lt;/h2&gt;

&lt;p&gt;I'll say it one more time because it's the most common mistake:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Access → Orientation → Understanding → Quotability
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your &lt;code&gt;robots.txt&lt;/code&gt; blocks GPTBot, your &lt;code&gt;llms.txt&lt;/code&gt; and schema are invisible. If your &lt;code&gt;llms.txt&lt;/code&gt; is broken, your schema sends a model to dead ends. If your schema is missing, your content structure doesn't help the model understand what it's reading.&lt;/p&gt;

&lt;p&gt;Fix the foundation first. Each layer enables the next.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Gets You
&lt;/h2&gt;

&lt;p&gt;From the benchmark data:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;With&lt;/th&gt;
&lt;th&gt;Without&lt;/th&gt;
&lt;th&gt;Gap&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;llms.txt&lt;/td&gt;
&lt;td&gt;63.3&lt;/td&gt;
&lt;td&gt;43.3&lt;/td&gt;
&lt;td&gt;+20.0 pts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Schema&lt;/td&gt;
&lt;td&gt;61.0&lt;/td&gt;
&lt;td&gt;32.8&lt;/td&gt;
&lt;td&gt;+28.2 pts&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Sites with &lt;code&gt;llms.txt&lt;/code&gt; score 20 points higher than sites without. Sites with schema score 28 points higher. The layers compound.&lt;/p&gt;

&lt;p&gt;None of this requires a marketing agency. It requires 30 minutes and a text editor. The four files you need to touch:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;/robots.txt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/llms.txt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Your homepage &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; (JSON-LD)&lt;/li&gt;
&lt;li&gt;Your top 5 pages (content structure)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Go fix them.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>seo</category>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>The 4-Layer Model for AI Search Readiness: What I Learned Auditing 360 Sites</title>
      <dc:creator>Juan Camilo Auriti</dc:creator>
      <pubDate>Mon, 03 Aug 2026 08:39:13 +0000</pubDate>
      <link>https://dev.to/juanauriti/the-4-layer-model-for-ai-search-readiness-what-i-learned-auditing-360-sites-h17</link>
      <guid>https://dev.to/juanauriti/the-4-layer-model-for-ai-search-readiness-what-i-learned-auditing-360-sites-h17</guid>
      <description>&lt;h2&gt;
  
  
  The 4-Layer Model for AI Search Readiness: What I Learned Auditing 360 Sites
&lt;/h2&gt;

&lt;p&gt;I audited 360 domains against a structured AI-search-readiness framework. The average score was &lt;strong&gt;54.1 out of 100&lt;/strong&gt;. Only &lt;strong&gt;24.7%&lt;/strong&gt; reached "Good" or above. The first perfect 100/100 didn't appear until July 2026.&lt;/p&gt;

&lt;p&gt;This post walks through the four-layer model I used — &lt;strong&gt;Access, Orientation, Understanding, Quotability&lt;/strong&gt; — with real code examples for each layer, the benchmark data behind it, and the infrastructure mistakes I kept seeing.&lt;/p&gt;

&lt;p&gt;If you're a developer or tech lead, this is the framework to operationalize before your marketing team asks why ChatGPT doesn't cite your docs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Thesis: AI Search Selects, It Doesn't Rank
&lt;/h2&gt;

&lt;p&gt;Traditional SEO is a ranking problem: optimize pages, climb positions, compete for slots on a results page.&lt;/p&gt;

&lt;p&gt;Generative search — ChatGPT, Perplexity, Google AI Overviews — is a &lt;strong&gt;selection problem&lt;/strong&gt;. A model receives a query, decides which sources to consult, extracts a passage, and either cites the source or paraphrases without attribution. There is no page two. You are either quoted, or you are invisible.&lt;/p&gt;

&lt;p&gt;Getting selected requires four things to work in sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Access&lt;/strong&gt; — the AI crawler can reach your content&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Orientation&lt;/strong&gt; — it can find what matters on your site&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Understanding&lt;/strong&gt; — it can parse what your organization and pages are about&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quotability&lt;/strong&gt; — it can extract a self-contained passage to quote&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The key principle: &lt;strong&gt;fix access before schema, fix schema before content.&lt;/strong&gt; Wrong order = wasted work. I saw this pattern repeatedly — teams rewriting content for "AI optimization" while their robots.txt blocked GPTBot.&lt;/p&gt;

&lt;p&gt;Let's go layer by layer with code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 1: Access — Can the AI Crawler Reach You?
&lt;/h2&gt;

&lt;p&gt;Access is robots.txt, static HTML delivery, server response behavior, and rendering. It's the least glamorous layer and the most common failure point.&lt;/p&gt;

&lt;p&gt;There are at least &lt;strong&gt;11 AI crawlers&lt;/strong&gt; actively indexing the web. Each has a distinct user-agent token:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Crawler&lt;/th&gt;
&lt;th&gt;User-Agent Token&lt;/th&gt;
&lt;th&gt;Operator&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GPTBot&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GPTBot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OpenAI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OAI-SearchBot&lt;/td&gt;
&lt;td&gt;&lt;code&gt;OAI-SearchBot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OpenAI (Search)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PerplexityBot&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PerplexityBot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Perplexity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ClaudeBot&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ClaudeBot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Anthropic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude-SearchBot&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Claude-SearchBot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Anthropic (Search)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Googlebot&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Googlebot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Google&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google-Extended&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Google-Extended&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Google (AI training)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Applebot&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Applebot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Apple&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CCBot&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CCBot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Common Crawl&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bytespider&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Bytespider&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ByteDance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Diffbot&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Diffbot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Diffbot&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The benchmark found an average of &lt;strong&gt;23.2 bots allowed&lt;/strong&gt; per site — but that average masks a long tail of sites blocking exactly the crawlers they need.&lt;/p&gt;

&lt;h3&gt;
  
  
  robots.txt: Allow the AI Crawlers
&lt;/h3&gt;

&lt;p&gt;Here's a minimal robots.txt that explicitly allows the major AI crawlers while keeping your private paths locked down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Allow major AI crawlers access to public content
User-agent: GPTBot
Allow: /

User-agent: OAI-SearchBot
Allow: /

User-agent: PerplexityBot
Allow: /

User-agent: ClaudeBot
Allow: /

User-agent: Claude-SearchBot
Allow: /

User-agent: Googlebot
Allow: /

User-agent: Google-Extended
Allow: /

User-agent: Applebot
Allow: /

User-agent: CCBot
Allow: /

User-agent: Bytespider
Allow: /

User-agent: Diffbot
Allow: /

# Block private/admin paths from all crawlers
User-agent: *
Disallow: /admin/
Disallow: /private/
Disallow: /api/internal/

# Sitemap
Sitemap: https://example.com/sitemap.xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Rendering Trap
&lt;/h3&gt;

&lt;p&gt;A subtler access issue: &lt;strong&gt;client-side rendering&lt;/strong&gt;. If your site is a SPA that returns an empty &lt;code&gt;&amp;lt;div id="root"&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt; on the initial HTML response, many AI crawlers see nothing. GPTBot and PerplexityBot do not execute JavaScript reliably. They read the static HTML.&lt;/p&gt;

&lt;p&gt;If your content lives behind a React/Vue/Svelte hydration step, you need either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SSR&lt;/strong&gt; (server-side rendering) or &lt;strong&gt;SSG&lt;/strong&gt; (static site generation) so the HTML contains the content, or&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;prerendering&lt;/strong&gt; layer that serves cached HTML to bot user-agents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check what a crawler actually sees:&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;-A&lt;/span&gt; &lt;span class="s2"&gt;"GPTBot"&lt;/span&gt; https://example.com | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"your main heading"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that returns nothing, your content is invisible to the crawler. Fix this before anything else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 2: Orientation — Can It Find What Matters?
&lt;/h2&gt;

&lt;p&gt;Once a crawler reaches your site, it needs to know what's important. Orientation covers &lt;strong&gt;llms.txt&lt;/strong&gt;, sitemaps, RSS feeds, and priority URL signals.&lt;/p&gt;

&lt;p&gt;This was the worst-performing category in the entire audit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AI Discovery adoption: 17.5%&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI Discovery efficiency: 10%&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The llms.txt numbers tell a specific story:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;llms.txt adoption: 54.2%&lt;/strong&gt; — but down from 58.3% in June. Sites are removing it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full llms.txt: only 26.9%&lt;/strong&gt; — the rest are partial or malformed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A broken llms.txt is worse than none. It sends a model a map with missing streets.&lt;/p&gt;

&lt;h3&gt;
  
  
  Minimal llms.txt Example
&lt;/h3&gt;

&lt;p&gt;The llms.txt standard is a plain-text file at the root of your site that gives AI crawlers a structured summary of your content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Example Company

&amp;gt; Example Company builds developer infrastructure for AI-powered search.

## Docs
- [Getting Started](https://example.com/docs/getting-started): Quick start guide
- [API Reference](https://example.com/docs/api): Full REST API documentation
- [SDK Guide](https://example.com/docs/sdk): SDK installation and usage

## Product
- [Features](https://example.com/features): Feature overview and comparison
- [Pricing](https://example.com/pricing): Pricing tiers and FAQ

## Blog
- [Blog Index](https://example.com/blog): Engineering and product blog

## Optional
- [About](https://example.com/about): Company background and team
- [Contact](https://example.com/contact): Contact information
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;#&lt;/code&gt; line is the site title&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;&amp;gt;&lt;/code&gt; line is a one-sentence summary&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;##&lt;/code&gt; sections group links&lt;/li&gt;
&lt;li&gt;Each link is &lt;code&gt;- [Title](URL): Description&lt;/code&gt; — the description matters, it gives the model context about what's at that URL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don't list every blog post. List the pages that answer "what is this site?" and "what does it do?"&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 3: Understanding — Can It Parse What You Are?
&lt;/h2&gt;

&lt;p&gt;A crawler has reached your site and found your priority content. Now it needs structured data to understand entities: who you are, what your site does, what questions your pages answer.&lt;/p&gt;

&lt;p&gt;Understanding is &lt;strong&gt;Schema.org JSON-LD&lt;/strong&gt;, meta tags, and entity signals.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benchmark Data
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Schema adoption: 75.6%&lt;/strong&gt; — up from 70.1% in June. Trending positive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Organization schema: 52.2%&lt;/strong&gt; — barely half of sites tell crawlers who they are.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;WebSite schema: 58.9%&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FAQ schema: 18.1%&lt;/strong&gt; — up from 13.2%, but still remarkably low for a schema type directly designed for question-answer extraction.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The gap between "has some schema" (75.6%) and "has the schema types that matter for AI citation" (52% / 59% / 18%) is where most sites lose ground.&lt;/p&gt;

&lt;h3&gt;
  
  
  Organization + WebSite JSON-LD Template
&lt;/h3&gt;

&lt;p&gt;Drop this in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; of your homepage. Replace the values with your real data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"application/ld+json"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@context&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://schema.org&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@graph&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Organization&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/#organization&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Example Company&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;url&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;logo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/logo.png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Example Company builds developer infrastructure for AI-powered search.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sameAs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://github.com/example&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://x.com/example&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://www.linkedin.com/company/example&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;contactPoint&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ContactPoint&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;contactType&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;support&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;support@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;url&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/contact&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WebSite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/#website&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;url&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Example Company&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Developer infrastructure for AI-powered search.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;publisher&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/#organization&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;potentialAction&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SearchAction&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;target&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;EntryPoint&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;urlTemplate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/search?q={search_term_string}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;query-input&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;required name=search_term_string&lt;/span&gt;&lt;span class="dl"&gt;"&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="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;@graph&lt;/code&gt; structure lets you declare multiple entities (Organization + WebSite) in one block and cross-reference them with &lt;code&gt;@id&lt;/code&gt;. This is how you tell a model "this organization publishes this website" — a relationship that matters for entity disambiguation.&lt;/p&gt;

&lt;p&gt;For FAQ pages, add this on the relevant page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"application/ld+json"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@context&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://schema.org&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FAQPage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mainEntity&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Question&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;What is AI search readiness?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;acceptedAnswer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Answer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AI search readiness is the degree to which a website can be discovered, understood, and cited by generative AI systems like ChatGPT, Perplexity, and Google AI Overviews.&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Question&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;How is GEO different from SEO?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;acceptedAnswer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Answer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SEO optimizes for ranking positions on a search results page. GEO optimizes for selection and citation by generative models that synthesize answers from multiple sources.&lt;/span&gt;&lt;span class="dl"&gt;"&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="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;FAQ schema is your direct line to question-answer extraction. When a model sees &lt;code&gt;FAQPage&lt;/code&gt; with &lt;code&gt;Question&lt;/code&gt; and &lt;code&gt;acceptedAnswer&lt;/code&gt; pairs, it can pull those answers verbatim. That's why the 18.1% adoption number is so painful — it's the schema type most directly tied to getting quoted, and 82% of sites don't have it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 4: Quotability — Can It Extract a Self-Contained Passage?
&lt;/h2&gt;

&lt;p&gt;The final layer is content architecture. A model can only quote you cleanly if your content is structured to be quoted: direct answers, bottom-line-up-front (BLUF), short self-contained paragraphs.&lt;/p&gt;

&lt;p&gt;This is the layer most teams skip — they fix robots.txt, add llms.txt, implement schema, and then leave their content as 2,000-word narratives that bury the answer in paragraph five.&lt;/p&gt;

&lt;h3&gt;
  
  
  Before: Narrative Structure (Hard to Quote)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# How Our API Handles Rate Limiting&lt;/span&gt;

When we first launched our API in 2023, we didn't have any rate limiting
in place. After a few incidents where a single client overwhelmed the
auth service, we realized we needed a more robust approach. We
experimented with token bucket algorithms, considered sliding window
loggers, and eventually settled on a fixed window counter approach
combined with exponential backoff. Here's how it works...

[800 more words of context, history, and implementation details]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A model reading this has to synthesize the answer from scattered sentences. It will likely paraphrase without citing, or skip this source for one that's cleaner.&lt;/p&gt;

&lt;h3&gt;
  
  
  After: BLUF Structure (Easy to Quote)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# How Our API Handles Rate Limiting&lt;/span&gt;

Our API enforces rate limiting using a &lt;span class="gs"&gt;**fixed window counter**&lt;/span&gt; with
exponential backoff. The default limit is &lt;span class="gs"&gt;**100 requests per minute**&lt;/span&gt;
per API key. When the limit is exceeded, the API returns HTTP 429 with
a &lt;span class="sb"&gt;`Retry-After`&lt;/span&gt; header indicating the wait time in seconds.

&lt;span class="gu"&gt;## How It Works&lt;/span&gt;

Rate limits are calculated per API key, not per IP address. Each
request increments a counter that resets at the start of each
60-second window. When the counter exceeds 100, subsequent requests
receive a 429 response until the window resets.

&lt;span class="gu"&gt;## Handling 429 Responses&lt;/span&gt;

Clients should implement exponential backoff: wait 1 second before
the first retry, then double the wait on each subsequent retry, up to
a maximum of 60 seconds. The &lt;span class="sb"&gt;`Retry-After`&lt;/span&gt; header provides the exact
wait time for the current window.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first paragraph is self-contained. A model can extract it as a direct quote: &lt;em&gt;"Our API enforces rate limiting using a fixed window counter with exponential backoff. The default limit is 100 requests per minute per API key."&lt;/em&gt; It makes sense outside the context of the full page. That's quotability.&lt;/p&gt;

&lt;p&gt;The rule: &lt;strong&gt;every page should open with a standalone answer to its core question.&lt;/strong&gt; Details go below. The answer goes first.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Correlation Data: What Actually Moves the Score
&lt;/h2&gt;

&lt;p&gt;The audit measured not just adoption but impact. The correlations are the strongest evidence for the 4-layer model:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;With&lt;/th&gt;
&lt;th&gt;Without&lt;/th&gt;
&lt;th&gt;Gap&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;llms.txt&lt;/td&gt;
&lt;td&gt;63.3&lt;/td&gt;
&lt;td&gt;43.3&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+20.0 pts&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Schema&lt;/td&gt;
&lt;td&gt;61.0&lt;/td&gt;
&lt;td&gt;32.8&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+28.2 pts&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Sites with llms.txt score 20 points higher than sites without. Sites with schema score 28 points higher. These aren't magic — a llms.txt file doesn't fix your server. But they show that the sites investing in orientation and understanding signals are the same sites that perform well across the board. The layers compound.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Infrastructure Mistakes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Blocking AI crawlers in robots.txt by accident.&lt;/strong&gt; A CMS, security plugin, or boilerplate template added &lt;code&gt;Disallow: /&lt;/code&gt; for &lt;code&gt;GPTBot&lt;/code&gt; and nobody reviewed it. Check your robots.txt with &lt;code&gt;curl https://example.com/robots.txt&lt;/code&gt; — actually read it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Client-side rendering with no prerender fallback.&lt;/strong&gt; Your SPA returns an empty shell. AI crawlers see nothing. Either use SSR/SSG or serve prerendered HTML to bot user-agents. Verify with &lt;code&gt;curl -A "GPTBot" https://example.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Partial llms.txt.&lt;/strong&gt; A llms.txt with broken links, missing sections, or only the homepage listed is worse than no llms.txt. The 27-point gap between adoption (54.2%) and full implementation (26.9%) means half the llms.txt files out there are broken maps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Generic schema only.&lt;/strong&gt; Article schema on blog posts is table stakes. The schema types that drive AI citation are &lt;strong&gt;Organization&lt;/strong&gt; (who are you?), &lt;strong&gt;WebSite&lt;/strong&gt; (what is this site?), and &lt;strong&gt;FAQPage&lt;/strong&gt; (what's the answer?). Most sites have none of these.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Fixing content before access.&lt;/strong&gt; I saw teams spend weeks rewriting content for "AI optimization" while their robots.txt blocked every AI crawler. The order is non-negotiable: Access → Orientation → Understanding → Quotability. Skip ahead and you're building on broken foundations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the Benchmark Stands
&lt;/h2&gt;

&lt;p&gt;360 domains audited. Average score 54.1/100. 75.3% at Foundation or Critical. The first 100/100 appeared in July.&lt;/p&gt;

&lt;p&gt;The gap between traditional SEO and GEO is measurable and structural. It's not about keywords or backlinks — it's about whether an AI crawler can reach your content, orient itself, understand your entities, and extract a clean passage. Four layers, in order, each one enabling the next.&lt;/p&gt;

&lt;p&gt;The good news for developers: every layer is fixable with infrastructure changes. robots.txt is a text file. llms.txt is a text file. JSON-LD is a script tag. BLUF is an editing pattern. None of this requires a marketing agency. It requires an engineer who knows the framework.&lt;/p&gt;

&lt;p&gt;Now you do.&lt;/p&gt;




&lt;p&gt;The full manual is 160 pages — 15 chapters covering all four layers in depth, 11 AI crawlers with user-agent tokens, 12 schema types with JSON-LD templates, 8 prompt injection attack vectors, and a citation measurement workflow. Free download at &lt;a href="https://geoready.dev/geo-readiness-manual/" rel="noopener noreferrer"&gt;geoready.dev/geo-readiness-manual/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>seo</category>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Why I Turned My Open-Source GEO Audit CLI Into a SaaS Monitoring Platform</title>
      <dc:creator>Juan Camilo Auriti</dc:creator>
      <pubDate>Thu, 04 Jun 2026 08:18:40 +0000</pubDate>
      <link>https://dev.to/juanauriti/why-i-turned-my-open-source-geo-audit-cli-into-a-saas-monitoring-platform-3ckl</link>
      <guid>https://dev.to/juanauriti/why-i-turned-my-open-source-geo-audit-cli-into-a-saas-monitoring-platform-3ckl</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnx71f6d84ykhmpk43jk1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnx71f6d84ykhmpk43jk1.png" alt="From open-source CLI to SaaS monitoring platform" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I did not start GEO Optimizer as a SaaS.&lt;/p&gt;

&lt;p&gt;I started it as a CLI.&lt;/p&gt;

&lt;p&gt;A small, local, inspectable Python tool with one basic question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can we audit whether a website is technically and semantically ready to be cited by AI search engines?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That was the first layer.&lt;/p&gt;

&lt;p&gt;Run a command.&lt;/p&gt;

&lt;p&gt;Get a score.&lt;/p&gt;

&lt;p&gt;Read the findings.&lt;/p&gt;

&lt;p&gt;Fix the obvious problems.&lt;/p&gt;

&lt;p&gt;Run it again.&lt;/p&gt;

&lt;p&gt;For developers, that workflow makes sense.&lt;/p&gt;

&lt;p&gt;Developers like tools that are local, scriptable, versionable, and easy to inspect. A CLI can be added to CI. It can run inside a terminal. It can produce JSON. It can be tested without a dashboard. It does not need a sales call or an enterprise plan.&lt;/p&gt;

&lt;p&gt;That is still the part of the project I care about most.&lt;/p&gt;

&lt;p&gt;But the more I worked on GEO Optimizer, the clearer one thing became:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A one-time GEO audit is useful, but AI search visibility is not a one-time problem.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is why I started building GeoReady.&lt;/p&gt;

&lt;p&gt;Not as a replacement for GEO Optimizer.&lt;/p&gt;

&lt;p&gt;As the continuity layer on top of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The CLI gives control. The SaaS gives continuity.
&lt;/h2&gt;

&lt;p&gt;The CLI is still the core.&lt;/p&gt;

&lt;p&gt;It is open source, MIT licensed, and designed for developers who want to audit websites locally.&lt;/p&gt;

&lt;p&gt;The SaaS exists because a different problem appears once the first audit is done.&lt;/p&gt;

&lt;p&gt;A team does not only need to know:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What is our GEO score today?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It also needs to know:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Did our score drop after the last redesign?&lt;/p&gt;

&lt;p&gt;Did a CMS update break structured data?&lt;/p&gt;

&lt;p&gt;Did a robots.txt change block an AI crawler?&lt;/p&gt;

&lt;p&gt;Did a content refresh improve citation readiness?&lt;/p&gt;

&lt;p&gt;Which recommendations were fixed, ignored, or regressed?&lt;/p&gt;

&lt;p&gt;Is the site becoming more understandable over time, or just changing?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is not a CLI-only problem anymore.&lt;/p&gt;

&lt;p&gt;That is a monitoring problem.&lt;/p&gt;

&lt;p&gt;A CLI can tell you what happened when you ran it.&lt;/p&gt;

&lt;p&gt;A SaaS can tell you what changed when nobody was watching.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F58ttj7zm0hm2lxos4a08.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F58ttj7zm0hm2lxos4a08.png" alt="CLI control compared to SaaS continuity" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  GEO is not only about being cited once
&lt;/h2&gt;

&lt;p&gt;One of the mistakes I see in early GEO conversations is treating AI search visibility like a fixed ranking position.&lt;/p&gt;

&lt;p&gt;It is not.&lt;/p&gt;

&lt;p&gt;LLM outputs are unstable.&lt;/p&gt;

&lt;p&gt;They are prompt-sensitive, model-sensitive, time-sensitive, retrieval-sensitive, and often non-deterministic.&lt;/p&gt;

&lt;p&gt;That does not make measurement useless.&lt;/p&gt;

&lt;p&gt;It makes measurement harder.&lt;/p&gt;

&lt;p&gt;A single AI answer snapshot is evidence.&lt;/p&gt;

&lt;p&gt;It is not the whole truth.&lt;/p&gt;

&lt;p&gt;If a model cites your site once, that is useful to know.&lt;/p&gt;

&lt;p&gt;If it does not cite you, that is useful too.&lt;/p&gt;

&lt;p&gt;But the more important question is usually deeper:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which underlying signals make a website more likely to be selected, cited, trusted, and reused across changing answer environments?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the layer I want GEO Optimizer and GeoReady to focus on.&lt;/p&gt;

&lt;p&gt;Not magic citation promises.&lt;/p&gt;

&lt;p&gt;Not "rank #1 in ChatGPT."&lt;/p&gt;

&lt;p&gt;Not a fake deterministic score for a non-deterministic environment.&lt;/p&gt;

&lt;p&gt;The practical layer is signal readiness:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;can the site be crawled?&lt;/li&gt;
&lt;li&gt;can the content be parsed?&lt;/li&gt;
&lt;li&gt;are entities clear?&lt;/li&gt;
&lt;li&gt;is structured data useful?&lt;/li&gt;
&lt;li&gt;are important claims grounded?&lt;/li&gt;
&lt;li&gt;are sources visible?&lt;/li&gt;
&lt;li&gt;is the content extractable?&lt;/li&gt;
&lt;li&gt;are AI discovery paths available?&lt;/li&gt;
&lt;li&gt;are there negative signals?&lt;/li&gt;
&lt;li&gt;can regressions be detected over time?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why the SaaS matters.&lt;/p&gt;

&lt;p&gt;The CLI can audit those signals.&lt;/p&gt;

&lt;p&gt;GeoReady can track them as an operational system.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with one-time audits
&lt;/h2&gt;

&lt;p&gt;A one-time audit is a snapshot.&lt;/p&gt;

&lt;p&gt;Snapshots are useful, but they age quickly.&lt;/p&gt;

&lt;p&gt;A website changes constantly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;marketing teams rewrite pages;&lt;/li&gt;
&lt;li&gt;developers ship redesigns;&lt;/li&gt;
&lt;li&gt;plugins update markup;&lt;/li&gt;
&lt;li&gt;CMS editors change headings;&lt;/li&gt;
&lt;li&gt;image alt text disappears;&lt;/li&gt;
&lt;li&gt;canonical tags break;&lt;/li&gt;
&lt;li&gt;structured data becomes stale;&lt;/li&gt;
&lt;li&gt;robots.txt rules change;&lt;/li&gt;
&lt;li&gt;JavaScript rendering assumptions shift;&lt;/li&gt;
&lt;li&gt;landing pages are duplicated;&lt;/li&gt;
&lt;li&gt;product pages lose factual density;&lt;/li&gt;
&lt;li&gt;internal linking gets weaker;&lt;/li&gt;
&lt;li&gt;schema is added but no longer matches the visible content.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In classic SEO, these regressions are already painful.&lt;/p&gt;

&lt;p&gt;In GEO, they become even more subtle because the problem is not only ranking.&lt;/p&gt;

&lt;p&gt;It is interpretability.&lt;/p&gt;

&lt;p&gt;A page can remain online and still become less useful to an AI answer engine.&lt;/p&gt;

&lt;p&gt;It may still load.&lt;/p&gt;

&lt;p&gt;It may still look good.&lt;/p&gt;

&lt;p&gt;It may still rank.&lt;/p&gt;

&lt;p&gt;But it may become harder to cite because the source clarity, factual grounding, or semantic structure got weaker.&lt;/p&gt;

&lt;p&gt;That is the kind of regression a single audit cannot catch.&lt;/p&gt;

&lt;h2&gt;
  
  
  What GeoReady adds on top of GEO Optimizer
&lt;/h2&gt;

&lt;p&gt;GeoReady is the web and SaaS layer built around the open-source audit engine.&lt;/p&gt;

&lt;p&gt;The public web audit is designed to make the first step easy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;enter a URL;&lt;/li&gt;
&lt;li&gt;get a 0-100 GEO score;&lt;/li&gt;
&lt;li&gt;see top recommendations;&lt;/li&gt;
&lt;li&gt;inspect part of the signal breakdown;&lt;/li&gt;
&lt;li&gt;decide whether the site is worth improving.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No account is required for the free audit.&lt;/p&gt;

&lt;p&gt;That matters because friction kills technical products.&lt;/p&gt;

&lt;p&gt;If someone has to create an account before understanding the problem, many people will leave before seeing the value.&lt;/p&gt;

&lt;p&gt;The free audit answers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is this site visibly weak or reasonably prepared?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The SaaS layer answers a different question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is this site improving or regressing over time?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is why the product direction includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;full 8-category reports;&lt;/li&gt;
&lt;li&gt;weekly monitoring;&lt;/li&gt;
&lt;li&gt;score history;&lt;/li&gt;
&lt;li&gt;regression alerts;&lt;/li&gt;
&lt;li&gt;PDF exports;&lt;/li&gt;
&lt;li&gt;API access;&lt;/li&gt;
&lt;li&gt;multi-domain monitoring;&lt;/li&gt;
&lt;li&gt;competitor comparison;&lt;/li&gt;
&lt;li&gt;team workflows;&lt;/li&gt;
&lt;li&gt;AI search snapshots;&lt;/li&gt;
&lt;li&gt;citation quality scoring.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not all of that should live in the CLI.&lt;/p&gt;

&lt;p&gt;Some of it requires storage.&lt;/p&gt;

&lt;p&gt;Some of it requires scheduled runs.&lt;/p&gt;

&lt;p&gt;Some of it requires account-level history.&lt;/p&gt;

&lt;p&gt;Some of it requires alerts.&lt;/p&gt;

&lt;p&gt;Some of it only becomes useful when a team can look back and see how the site changed over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping the core open source
&lt;/h2&gt;

&lt;p&gt;This was important to me from the beginning.&lt;/p&gt;

&lt;p&gt;I do not want GEO to become a black box category where every tool says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Trust our proprietary score.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is not healthy for developers, SEO specialists, founders, agencies, or content teams.&lt;/p&gt;

&lt;p&gt;If we are going to build tools that evaluate AI search visibility, the methodology should be inspectable.&lt;/p&gt;

&lt;p&gt;The scoring logic should be discussable.&lt;/p&gt;

&lt;p&gt;The assumptions should be visible.&lt;/p&gt;

&lt;p&gt;The false positives should be debuggable.&lt;/p&gt;

&lt;p&gt;The CLI should remain useful even if someone never becomes a SaaS customer.&lt;/p&gt;

&lt;p&gt;That is why the core remains open source.&lt;/p&gt;

&lt;p&gt;GEO Optimizer is the engine.&lt;/p&gt;

&lt;p&gt;GeoReady is the operational layer.&lt;/p&gt;

&lt;p&gt;The distinction matters.&lt;/p&gt;

&lt;p&gt;The open-source project is for local audits, CI/CD workflows, experimentation, research, and transparency.&lt;/p&gt;

&lt;p&gt;The SaaS is for continuity: monitoring, history, alerts, reports, exports, and team workflows.&lt;/p&gt;

&lt;p&gt;That is the model I am trying to build.&lt;/p&gt;

&lt;p&gt;Not open source as a marketing trick.&lt;/p&gt;

&lt;p&gt;Open source as the foundation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the JSON contract matters
&lt;/h2&gt;

&lt;p&gt;One of the less glamorous but very important parts of the 4.12.x series is the JSON contract.&lt;/p&gt;

&lt;p&gt;As soon as a CLI becomes the foundation for a web product, the output cannot be casual anymore.&lt;/p&gt;

&lt;p&gt;A command-line report can be flexible.&lt;/p&gt;

&lt;p&gt;A web platform needs stable data.&lt;/p&gt;

&lt;p&gt;The frontend needs predictable fields.&lt;/p&gt;

&lt;p&gt;API users need predictable fields.&lt;/p&gt;

&lt;p&gt;Tests need predictable fixtures.&lt;/p&gt;

&lt;p&gt;Reports need stable structures.&lt;/p&gt;

&lt;p&gt;Monitoring needs comparable values over time.&lt;/p&gt;

&lt;p&gt;If the audit response shape changes randomly, everything above the engine becomes fragile.&lt;/p&gt;

&lt;p&gt;That is why the JSON contract matters.&lt;/p&gt;

&lt;p&gt;In the 4.12.x series, the audit API response now includes a schema version and a more stable structure for score breakdowns and checks.&lt;/p&gt;

&lt;p&gt;This is not the kind of feature that gets attention on social media.&lt;/p&gt;

&lt;p&gt;But it is the kind of feature that makes the product real.&lt;/p&gt;

&lt;p&gt;A SaaS cannot be built on vibes.&lt;/p&gt;

&lt;p&gt;It needs contracts.&lt;/p&gt;

&lt;p&gt;It needs regression tests.&lt;/p&gt;

&lt;p&gt;It needs compatibility rules.&lt;/p&gt;

&lt;p&gt;It needs stable boundaries between engine, API, frontend, and reporting.&lt;/p&gt;

&lt;p&gt;That was one of the main lessons from turning the CLI into a platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why v4.12.2 matters
&lt;/h2&gt;

&lt;p&gt;v4.12.2 is not a flashy release.&lt;/p&gt;

&lt;p&gt;It fixes a CI/PyPI publishing blocker related to FastAPI imports in tests.&lt;/p&gt;

&lt;p&gt;That may sound small, but it matters because it unblocked the 4.12.x series on PyPI.&lt;/p&gt;

&lt;p&gt;It also means the package can ship with the important 4.12.0 work included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Google AI scoring realignment;&lt;/li&gt;
&lt;li&gt;JSON contract v1;&lt;/li&gt;
&lt;li&gt;frontend redesign;&lt;/li&gt;
&lt;li&gt;safer web/API contract behavior.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Google AI scoring realignment is especially important.&lt;/p&gt;

&lt;p&gt;I do not want GEO Optimizer to reward signals for Google AI that Google does not claim to use.&lt;/p&gt;

&lt;p&gt;So the Google AI lens was adjusted away from rewarding AI-specific files such as &lt;code&gt;llms.txt&lt;/code&gt; or &lt;code&gt;ai.txt&lt;/code&gt; for Google AI, and toward signals that are more aligned with Google's documented guidance: freshness, server-rendered accessible content, descriptive alt text, schema, and entity clarity.&lt;/p&gt;

&lt;p&gt;That is a practical example of how I want this project to evolve.&lt;/p&gt;

&lt;p&gt;Not by adding fashionable checks.&lt;/p&gt;

&lt;p&gt;By making the scoring more defensible.&lt;/p&gt;

&lt;h2&gt;
  
  
  GEO and SEO are converging
&lt;/h2&gt;

&lt;p&gt;I do not think GEO replaces SEO.&lt;/p&gt;

&lt;p&gt;I think GEO makes SEO more layered.&lt;/p&gt;

&lt;p&gt;A user may ask an AI system:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is this product any good?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then they may verify the answer on Google.&lt;/p&gt;

&lt;p&gt;They may check reviews.&lt;/p&gt;

&lt;p&gt;They may open the website.&lt;/p&gt;

&lt;p&gt;They may compare competitors.&lt;/p&gt;

&lt;p&gt;They may look for documentation, pricing, schema-rich snippets, author information, support pages, and reputation signals.&lt;/p&gt;

&lt;p&gt;If a brand is visible in AI answers but weak in classic search, the citation may not convert.&lt;/p&gt;

&lt;p&gt;If a brand is strong in SEO but absent from AI-generated answers, it may lose the first-answer layer.&lt;/p&gt;

&lt;p&gt;The same content pipeline increasingly needs to serve both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;traditional crawlability;&lt;/li&gt;
&lt;li&gt;structured data;&lt;/li&gt;
&lt;li&gt;helpful content;&lt;/li&gt;
&lt;li&gt;entity clarity;&lt;/li&gt;
&lt;li&gt;citation readiness;&lt;/li&gt;
&lt;li&gt;factual grounding;&lt;/li&gt;
&lt;li&gt;AI answer extractability;&lt;/li&gt;
&lt;li&gt;trust and verification signals.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is why I do not see GeoReady as a traditional SEO tool.&lt;/p&gt;

&lt;p&gt;It is not trying to replace rank trackers, backlink tools, or keyword research platforms.&lt;/p&gt;

&lt;p&gt;It is trying to answer a more specific question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can AI answer engines read, understand, trust, and cite this site?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And then:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is that readiness improving or getting worse over time?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The SaaS is not only a dashboard
&lt;/h2&gt;

&lt;p&gt;It would be easy to build a dashboard that displays a score and call it a SaaS.&lt;/p&gt;

&lt;p&gt;That is not enough.&lt;/p&gt;

&lt;p&gt;A score is only useful if it leads to action.&lt;/p&gt;

&lt;p&gt;For GeoReady, the product loop I care about is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;audit the site;&lt;/li&gt;
&lt;li&gt;identify weak signals;&lt;/li&gt;
&lt;li&gt;prioritize fixes;&lt;/li&gt;
&lt;li&gt;apply changes;&lt;/li&gt;
&lt;li&gt;re-audit;&lt;/li&gt;
&lt;li&gt;compare the difference;&lt;/li&gt;
&lt;li&gt;monitor the domain;&lt;/li&gt;
&lt;li&gt;alert when regressions happen;&lt;/li&gt;
&lt;li&gt;report progress to a client, team, or founder.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That loop is much more valuable than a static number.&lt;/p&gt;

&lt;p&gt;The score is the entry point.&lt;/p&gt;

&lt;p&gt;The workflow is the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this is going
&lt;/h2&gt;

&lt;p&gt;The near-term direction is straightforward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;improve the public audit experience;&lt;/li&gt;
&lt;li&gt;make recommendations more actionable;&lt;/li&gt;
&lt;li&gt;strengthen report clarity;&lt;/li&gt;
&lt;li&gt;keep the CLI reliable and free;&lt;/li&gt;
&lt;li&gt;improve the web platform;&lt;/li&gt;
&lt;li&gt;expand monitoring and history;&lt;/li&gt;
&lt;li&gt;make alerts useful instead of noisy;&lt;/li&gt;
&lt;li&gt;expose better API workflows;&lt;/li&gt;
&lt;li&gt;support agencies and consultants managing multiple domains;&lt;/li&gt;
&lt;li&gt;keep the scoring methodology transparent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The longer-term direction is more ambitious:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;compare AI search snapshots across systems;&lt;/li&gt;
&lt;li&gt;score citation quality, not just citation presence;&lt;/li&gt;
&lt;li&gt;detect factual accuracy risks;&lt;/li&gt;
&lt;li&gt;track brand/entity representation;&lt;/li&gt;
&lt;li&gt;connect audit results with content workflows;&lt;/li&gt;
&lt;li&gt;support CI/CD checks for AI search readiness;&lt;/li&gt;
&lt;li&gt;make GEO measurable without pretending it is deterministic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last part matters.&lt;/p&gt;

&lt;p&gt;I do not want to pretend that AI search works like a stable list of ten blue links.&lt;/p&gt;

&lt;p&gt;It does not.&lt;/p&gt;

&lt;p&gt;But I also do not believe the correct response is to stop measuring.&lt;/p&gt;

&lt;p&gt;The correct response is to measure differently.&lt;/p&gt;

&lt;h2&gt;
  
  
  The product principle
&lt;/h2&gt;

&lt;p&gt;The principle I keep coming back to is this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Open source for trust. SaaS for continuity.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The open-source engine keeps the methodology visible.&lt;/p&gt;

&lt;p&gt;The SaaS turns the audit into an ongoing operational workflow.&lt;/p&gt;

&lt;p&gt;That is the balance I want.&lt;/p&gt;

&lt;p&gt;A local CLI is perfect for developers who want control.&lt;/p&gt;

&lt;p&gt;A web audit is perfect for fast diagnosis.&lt;/p&gt;

&lt;p&gt;A SaaS monitoring layer is useful when the problem becomes continuous.&lt;/p&gt;

&lt;p&gt;Those are not competing products.&lt;/p&gt;

&lt;p&gt;They are different layers of the same system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;You can try the public audit here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://geoready.dev" rel="noopener noreferrer"&gt;https://geoready.dev?utm_source=dev.to&amp;amp;utm_medium=dev.to&amp;amp;utm_campaign=dev_to_saas_article&amp;amp;utm_content=dev_to_article_saas_ded&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can create an account for the platform here:&lt;/p&gt;

&lt;p&gt;[&lt;a href="https://app.geoready.dev?utm_source=dev.to&amp;amp;utm_medium=dev.to&amp;amp;utm_campaign=dev_to_saas_registration&amp;amp;utm_content=dev_to_article_saas_ded_reg%5Dhttps://app.geoready.dev"&gt;https://app.geoready.dev?utm_source=dev.to&amp;amp;utm_medium=dev.to&amp;amp;utm_campaign=dev_to_saas_registration&amp;amp;utm_content=dev_to_article_saas_ded_reg]https://app.geoready.dev&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can inspect the open-source engine here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Auriti-Labs/geo-optimizer-skill" rel="noopener noreferrer"&gt;https://github.com/Auriti-Labs/geo-optimizer-skill&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can install the CLI with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;geo-optimizer-skill&lt;span class="o"&gt;==&lt;/span&gt;4.12.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or with the web extra:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="s2"&gt;"geo-optimizer-skill[web]==4.12.2"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are building in SEO, GEO, AI search, content operations, or developer tooling, I would be especially interested in feedback on one question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What should a GEO monitoring platform help you fix after it tells you that your site is invisible or weak?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Because that is the part I think matters most.&lt;/p&gt;

&lt;p&gt;Not only tracking visibility.&lt;/p&gt;

&lt;p&gt;Improving the signals behind it.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>saas</category>
    </item>
    <item>
      <title>The Missing Layer in Google I/O 2026: Agent-Ready Websites</title>
      <dc:creator>Juan Camilo Auriti</dc:creator>
      <pubDate>Wed, 20 May 2026 13:25:08 +0000</pubDate>
      <link>https://dev.to/juanauriti/the-missing-layer-in-google-io-2026-agent-ready-websites-4p7f</link>
      <guid>https://dev.to/juanauriti/the-missing-layer-in-google-io-2026-agent-ready-websites-4p7f</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/google-io-writing-2026-05-19"&gt;Google I/O Writing Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Everyone is talking about Gemini, Antigravity, AI Mode, and coding agents.&lt;/p&gt;

&lt;p&gt;I want to talk about websites.&lt;/p&gt;

&lt;p&gt;Because the Google I/O 2026 announcement that spoke to me most was not only a model announcement, an IDE update, or another productivity feature. It was the larger pattern behind several announcements: the web is becoming agentic.&lt;/p&gt;

&lt;p&gt;That changes the job of a web developer.&lt;/p&gt;

&lt;p&gt;For years, we built websites mainly for three audiences:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;humans using browsers;&lt;/li&gt;
&lt;li&gt;crawlers indexing pages;&lt;/li&gt;
&lt;li&gt;search engines ranking documents.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After Google I/O 2026, I think we need to add a fourth audience:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;AI agents that read, reason, compare, inspect, click, call tools, and act on behalf of users.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That does not mean we should stop caring about humans. Quite the opposite. The best agent-ready website is usually a better human website too: clearer, more semantic, more accessible, more stable, and less ambiguous.&lt;/p&gt;

&lt;p&gt;But it does mean that “looking good in the browser” is no longer enough.&lt;/p&gt;

&lt;p&gt;A website now has to answer a deeper question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can an AI system understand what this page means, verify what it claims, and safely act on what it exposes?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That, to me, is the missing layer in Google I/O 2026: agent-ready websites.&lt;/p&gt;




&lt;h2&gt;
  
  
  The announcement I am focusing on: WebMCP and the agentic web
&lt;/h2&gt;

&lt;p&gt;Google I/O 2026 had many AI announcements. Google’s own I/O 2026 recap frames the event around more capable models, agentic experiences, Antigravity, Information agents in Search, Gemini Spark, Universal Cart, and broader Gemini integration across products.&lt;/p&gt;

&lt;p&gt;But the announcement that feels most underrated for web developers is the Chrome team’s work around the agentic web, especially &lt;a href="https://developer.chrome.com/blog/webmcp-epp" rel="noopener noreferrer"&gt;WebMCP&lt;/a&gt; and the broader set of Chrome updates announced in “&lt;a href="https://developer.chrome.com/blog/chrome-at-io26" rel="noopener noreferrer"&gt;15 updates from Google I/O 2026: Powering the agentic web&lt;/a&gt;”.&lt;/p&gt;

&lt;p&gt;The key idea behind WebMCP is simple but powerful:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Websites should be able to expose structured tools that browser-based agents can use with more reliability, precision, and context.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Instead of forcing an agent to visually guess its way through a complex interface, WebMCP aims to let a website describe actions in a more machine-friendly way. The Chrome team describes it as a proposed open web standard for exposing structured tools such as JavaScript functions and HTML forms to browser-based agents.&lt;/p&gt;

&lt;p&gt;That matters because it changes the role of the website.&lt;/p&gt;

&lt;p&gt;A website is no longer only a set of pages.&lt;/p&gt;

&lt;p&gt;It can become a set of understandable capabilities.&lt;/p&gt;

&lt;p&gt;For example, instead of an agent trying to click through a travel booking flow step by step, a site could expose a structured tool for checking availability, comparing options, or building an itinerary. The user would still need control and approval where appropriate, but the interaction becomes more explicit and less fragile.&lt;/p&gt;

&lt;p&gt;This is not just automation.&lt;/p&gt;

&lt;p&gt;This is a new contract between websites and agents.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this matters beyond WebMCP
&lt;/h2&gt;

&lt;p&gt;The important part is not only WebMCP itself.&lt;/p&gt;

&lt;p&gt;The important part is the direction.&lt;/p&gt;

&lt;p&gt;Google I/O 2026 repeatedly pointed toward AI systems that do not merely answer questions, but help users complete tasks. Google described Gemini 3.5 as a family of models combining “frontier intelligence with action”, with Gemini 3.5 Flash positioned for agentic and coding workflows. Google’s I/O 2026 collection also highlights Information agents in Search, Gemini Spark, Universal Cart, and an agent-first development platform in Antigravity.&lt;/p&gt;

&lt;p&gt;In parallel, Google Search Central published guidance for optimizing websites for generative AI features in Search. That guide keeps SEO in the picture, but reframes the environment: generative AI features use techniques such as retrieval-augmented generation and query fan-out, and Google explicitly encourages clear technical structure, crawlability, unique non-commodity content, and agentic-experience awareness.&lt;/p&gt;

&lt;p&gt;Then web.dev published guidance on &lt;a href="https://web.dev/articles/ai-agent-site-ux" rel="noopener noreferrer"&gt;building agent-friendly websites&lt;/a&gt;, making the point even more concrete: agents can interpret websites through multiple signals, including screenshots, DOM structure, and the accessibility tree.&lt;/p&gt;

&lt;p&gt;For me, these announcements are connected.&lt;/p&gt;

&lt;p&gt;Search is becoming more generative.&lt;/p&gt;

&lt;p&gt;Browsers are becoming more agentic.&lt;/p&gt;

&lt;p&gt;Developer tools are becoming more autonomous.&lt;/p&gt;

&lt;p&gt;And websites are becoming inputs to systems that do not behave like traditional users.&lt;/p&gt;

&lt;p&gt;That is a big deal.&lt;/p&gt;




&lt;h2&gt;
  
  
  The old web contract is changing
&lt;/h2&gt;

&lt;p&gt;The old web contract looked something like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;make the page discoverable;&lt;/li&gt;
&lt;li&gt;make the content indexable;&lt;/li&gt;
&lt;li&gt;make the interface usable by humans;&lt;/li&gt;
&lt;li&gt;make the page fast enough;&lt;/li&gt;
&lt;li&gt;make the content rank.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That contract still matters.&lt;/p&gt;

&lt;p&gt;But it is no longer complete.&lt;/p&gt;

&lt;p&gt;The new contract adds questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;can an AI system retrieve the correct information from this page?&lt;/li&gt;
&lt;li&gt;can it distinguish primary content from decorative noise?&lt;/li&gt;
&lt;li&gt;can it identify the purpose of a button, form, or control?&lt;/li&gt;
&lt;li&gt;can it understand pricing, availability, dates, policies, constraints, and exceptions?&lt;/li&gt;
&lt;li&gt;can it use the accessibility tree as a reliable functional map?&lt;/li&gt;
&lt;li&gt;can it perform an action without guessing?&lt;/li&gt;
&lt;li&gt;can it avoid dangerous or irreversible steps without user confirmation?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxevteujb15btq5pzlv86.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxevteujb15btq5pzlv86.png" alt="Old Web Contract vs New Web Contract diagram comparing the traditional web model focused on indexing, ranking, and human usability with the new agent-ready web model focused on retrieval, semantic structure, accessibility tree quality, and actionable interfaces for AI agents." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is where agent readiness begins.&lt;/p&gt;

&lt;p&gt;It is not “SEO with a different name”.&lt;/p&gt;

&lt;p&gt;It is not “add one magic file and hope AI systems cite you”.&lt;/p&gt;

&lt;p&gt;It is the discipline of reducing ambiguity across content, markup, interface behavior, and actions.&lt;/p&gt;




&lt;h2&gt;
  
  
  SEO is still relevant, but it is not the whole story
&lt;/h2&gt;

&lt;p&gt;One of the most useful parts of Google’s new generative AI search guidance is its refusal to treat AI search as a completely separate universe.&lt;/p&gt;

&lt;p&gt;Google says foundational SEO practices still matter because generative AI features in Search are rooted in core Search ranking and quality systems. That is important. Crawlability, indexability, technical clarity, helpful content, good page experience, and structured information are not obsolete.&lt;/p&gt;

&lt;p&gt;But I do not read this as “nothing has changed”.&lt;/p&gt;

&lt;p&gt;I read it as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;SEO remains the foundation, but AI agents add a new interaction layer on top of that foundation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A page can be indexed and still be hard for an agent to use.&lt;/p&gt;

&lt;p&gt;A page can rank and still expose a confusing form.&lt;/p&gt;

&lt;p&gt;A page can have structured data and still hide critical context behind vague UI labels.&lt;/p&gt;

&lt;p&gt;A page can be beautiful and still be functionally broken for non-human navigation.&lt;/p&gt;

&lt;p&gt;This is especially true when the task is not “find a document”, but “complete a journey”.&lt;/p&gt;

&lt;p&gt;Finding a hotel page is one thing.&lt;/p&gt;

&lt;p&gt;Understanding room types, checking dates, comparing cancellation policies, selecting accessibility options, and preparing a booking request is another.&lt;/p&gt;

&lt;p&gt;That second experience is where agent readiness matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  My critique: the agentic web could reward clarity — or punish fragile websites
&lt;/h2&gt;

&lt;p&gt;My biggest concern is that agentic browsing may widen the gap between well-engineered websites and fragile interfaces.&lt;/p&gt;

&lt;p&gt;A clean, semantic, accessible website may become easier for agents to understand and operate.&lt;/p&gt;

&lt;p&gt;A visually impressive but structurally confusing website may become less useful in practice, even if it looks good to humans.&lt;/p&gt;

&lt;p&gt;That matters because many modern interfaces are optimized for visual polish but not for semantic clarity.&lt;/p&gt;

&lt;p&gt;We use clickable divs instead of buttons.&lt;/p&gt;

&lt;p&gt;We hide labels because the placeholder looks cleaner.&lt;/p&gt;

&lt;p&gt;We build custom controls that look beautiful but expose weak roles and names.&lt;/p&gt;

&lt;p&gt;We rely on hover states that do not translate well to automation.&lt;/p&gt;

&lt;p&gt;We split critical information across modals, accordions, carousels, and animation-heavy layouts.&lt;/p&gt;

&lt;p&gt;We make forms that humans can eventually understand, but machines must infer.&lt;/p&gt;

&lt;p&gt;For humans, this is annoying.&lt;/p&gt;

&lt;p&gt;For agents, it is brittle.&lt;/p&gt;

&lt;p&gt;And for developers, it is a warning.&lt;/p&gt;

&lt;p&gt;The agentic web will not only need better models. It will need better websites.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Agent Readiness Stack
&lt;/h2&gt;

&lt;p&gt;Here is the mental model I would use after Google I/O 2026.&lt;/p&gt;

&lt;p&gt;I call it the Agent Readiness Stack.&lt;/p&gt;

&lt;p&gt;It has five layers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbh7sa1lcdtuqkr63mvxh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbh7sa1lcdtuqkr63mvxh.png" alt="Five-layer vertical stack diagram titled “Agent Readiness Stack”, showing Crawlability, Content Clarity, Semantic Structure, Accessibility Tree Quality, and Action Readiness as progressive layers between a website and an AI agent." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Crawlability
&lt;/h3&gt;

&lt;p&gt;Before an agent can reason about your content, the content has to be discoverable and accessible.&lt;/p&gt;

&lt;p&gt;This is the layer traditional SEO already knows well:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;do not block important pages accidentally;&lt;/li&gt;
&lt;li&gt;make critical content publicly reachable when it should be;&lt;/li&gt;
&lt;li&gt;use sensible internal linking;&lt;/li&gt;
&lt;li&gt;avoid unnecessary duplication;&lt;/li&gt;
&lt;li&gt;keep JavaScript-rendered content accessible to search systems;&lt;/li&gt;
&lt;li&gt;maintain clean status codes, canonical URLs, and sitemaps where appropriate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not glamorous, but it is foundational.&lt;/p&gt;

&lt;p&gt;If the page cannot be found, nothing else matters.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Content clarity
&lt;/h3&gt;

&lt;p&gt;Generative AI search makes weak content easier to ignore.&lt;/p&gt;

&lt;p&gt;Google’s guidance emphasizes unique, valuable, non-commodity content. That phrase matters because AI systems are increasingly good at summarizing common knowledge. If your page only repeats what everyone else says, there may be little reason to retrieve it, cite it, or send a user to it.&lt;/p&gt;

&lt;p&gt;For developers and site owners, this means content should answer questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what do we know from direct experience?&lt;/li&gt;
&lt;li&gt;what can we explain better than generic sources?&lt;/li&gt;
&lt;li&gt;what details would help a user make a real decision?&lt;/li&gt;
&lt;li&gt;what constraints, trade-offs, edge cases, or risks should be visible?&lt;/li&gt;
&lt;li&gt;what claims can be verified?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Agent-ready content is not necessarily longer.&lt;/p&gt;

&lt;p&gt;It is clearer.&lt;/p&gt;

&lt;p&gt;It makes the important facts explicit.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Semantic structure
&lt;/h3&gt;

&lt;p&gt;HTML is not just a rendering target.&lt;/p&gt;

&lt;p&gt;It is meaning.&lt;/p&gt;

&lt;p&gt;When an interface is built with semantic elements, the browser, assistive technologies, crawlers, and agents have a better chance of understanding what is happening.&lt;/p&gt;

&lt;p&gt;That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use real headings for structure;&lt;/li&gt;
&lt;li&gt;use real buttons for actions;&lt;/li&gt;
&lt;li&gt;use links for navigation;&lt;/li&gt;
&lt;li&gt;connect labels to inputs;&lt;/li&gt;
&lt;li&gt;expose names, roles, and states correctly;&lt;/li&gt;
&lt;li&gt;avoid replacing native controls with fragile custom components unless there is a strong reason;&lt;/li&gt;
&lt;li&gt;make important information part of the document, not only a visual decoration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not about writing perfect HTML for its own sake.&lt;/p&gt;

&lt;p&gt;It is about reducing interpretation errors.&lt;/p&gt;

&lt;p&gt;A human can sometimes guess that a styled card is clickable.&lt;/p&gt;

&lt;p&gt;An agent should not have to guess.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Accessibility tree quality
&lt;/h3&gt;

&lt;p&gt;The accessibility tree may become one of the most important debugging surfaces for agent-ready websites.&lt;/p&gt;

&lt;p&gt;web.dev describes the accessibility tree as a browser-native representation that distills the DOM into roles, names, and states of interactive elements. For assistive technology, it is essential. For agents, it can become a functional map of the page.&lt;/p&gt;

&lt;p&gt;That means accessibility is not a separate checklist anymore.&lt;/p&gt;

&lt;p&gt;It is part of AI usability.&lt;/p&gt;

&lt;p&gt;If a button has no accessible name, a screen reader user suffers.&lt;/p&gt;

&lt;p&gt;If a form field has no label, an agent may not understand what value belongs there.&lt;/p&gt;

&lt;p&gt;If a custom control exposes the wrong role, automation becomes unreliable.&lt;/p&gt;

&lt;p&gt;Accessibility work has always been about inclusion.&lt;/p&gt;

&lt;p&gt;Now it is also becoming part of machine interpretability.&lt;/p&gt;

&lt;p&gt;That should not reduce its human importance. It should increase its priority.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Action readiness
&lt;/h3&gt;

&lt;p&gt;This is the new layer.&lt;/p&gt;

&lt;p&gt;Can an agent safely act on the site?&lt;/p&gt;

&lt;p&gt;Not every website needs this immediately. A blog post may only need to be readable and citeable. But ecommerce, travel, SaaS, local services, booking systems, dashboards, and support portals increasingly need to think about actions.&lt;/p&gt;

&lt;p&gt;Actions require more than buttons.&lt;/p&gt;

&lt;p&gt;They require intent, constraints, parameters, validation, and confirmation.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Search rooms” is safer than “Submit”.&lt;/li&gt;
&lt;li&gt;“Request booking quote” is clearer than “Continue”.&lt;/li&gt;
&lt;li&gt;“Cancel subscription” must require explicit confirmation.&lt;/li&gt;
&lt;li&gt;“Pay now” must never be hidden behind ambiguous automation.&lt;/li&gt;
&lt;li&gt;“Compare plans” should expose plan names, prices, limits, and billing periods clearly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;WebMCP is interesting because it points toward a world where websites can expose those actions as structured tools instead of leaving agents to infer everything from pixels and DOM fragments.&lt;/p&gt;

&lt;p&gt;That does not remove the need for UX.&lt;/p&gt;

&lt;p&gt;It makes UX more explicit.&lt;/p&gt;




&lt;h2&gt;
  
  
  A small example: from a fragile form to an agent-readable form
&lt;/h2&gt;

&lt;p&gt;Imagine a booking form.&lt;/p&gt;

&lt;p&gt;A fragile version might look clean to humans but confusing to agents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"booking-card"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"field"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Arrival&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Select date"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"field"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Leaving&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Select date"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"fake-button"&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"submitBooking()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Continue
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This may work visually, but it creates ambiguity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the inputs do not have stable labels;&lt;/li&gt;
&lt;li&gt;the fields do not have clear names;&lt;/li&gt;
&lt;li&gt;the action is a clickable div;&lt;/li&gt;
&lt;li&gt;the button text is generic;&lt;/li&gt;
&lt;li&gt;the expected data format is unclear;&lt;/li&gt;
&lt;li&gt;the user intent is hidden inside JavaScript.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A more agent-ready version starts with ordinary good HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"/booking/search"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"get"&lt;/span&gt; &lt;span class="na"&gt;aria-labelledby=&lt;/span&gt;&lt;span class="s"&gt;"booking-search-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"booking-search-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Search room availability&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"check-in"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Check-in date&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"check-in"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"check_in"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"date"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"check-out"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Check-out date&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"check-out"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"check_out"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"date"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"guests"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Number of guests&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"guests"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"guests"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"number"&lt;/span&gt; &lt;span class="na"&gt;min=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;max=&lt;/span&gt;&lt;span class="s"&gt;"6"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Search available rooms&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not futuristic.&lt;/p&gt;

&lt;p&gt;It is just clear.&lt;/p&gt;

&lt;p&gt;But that is the point.&lt;/p&gt;

&lt;p&gt;Agent readiness often starts with things web developers should already care about: semantic HTML, accessible names, explicit labels, stable actions, and meaningful copy.&lt;/p&gt;

&lt;p&gt;If you later experiment with WebMCP, that same clarity becomes even more valuable. Current Lighthouse documentation for agentic browsing already points to schema validity concepts such as tool names, tool descriptions, input names, labels, and parameter descriptions.&lt;/p&gt;

&lt;p&gt;The direction is obvious: agents need structured intent, not visual guesswork.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fswd2lls2mofr8bt8gszl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fswd2lls2mofr8bt8gszl.png" alt="Split-screen technical illustration comparing a fragile visual-only booking form with an agent-ready semantic form using labels, real buttons, clear field names, and structured actions." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  A practical checklist for developers
&lt;/h2&gt;

&lt;p&gt;If I had to turn Google I/O 2026 into a practical checklist for everyday web work, I would start here.&lt;/p&gt;

&lt;h3&gt;
  
  
  Content
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Do not publish generic summaries that anyone could produce.&lt;/li&gt;
&lt;li&gt;Add first-hand experience, examples, constraints, and trade-offs.&lt;/li&gt;
&lt;li&gt;Make claims easy to verify.&lt;/li&gt;
&lt;li&gt;Keep dates, prices, availability, version numbers, and eligibility conditions explicit.&lt;/li&gt;
&lt;li&gt;Use headings that describe the content, not vague marketing slogans.&lt;/li&gt;
&lt;li&gt;Add useful images or diagrams when they clarify the topic.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  HTML and interface structure
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Use native buttons for actions.&lt;/li&gt;
&lt;li&gt;Use links for navigation.&lt;/li&gt;
&lt;li&gt;Avoid clickable non-interactive elements where possible.&lt;/li&gt;
&lt;li&gt;Connect every form input with a visible or programmatically available label.&lt;/li&gt;
&lt;li&gt;Use meaningful name attributes for form fields.&lt;/li&gt;
&lt;li&gt;Make error messages specific and connected to the relevant fields.&lt;/li&gt;
&lt;li&gt;Avoid hiding critical information only inside hover states or visual-only interactions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Accessibility tree
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Inspect the accessibility tree in Chrome DevTools.&lt;/li&gt;
&lt;li&gt;Check whether every important action has a clear accessible name.&lt;/li&gt;
&lt;li&gt;Verify that custom components expose correct roles, names, and states.&lt;/li&gt;
&lt;li&gt;Test keyboard navigation.&lt;/li&gt;
&lt;li&gt;Avoid layouts where the visual order and DOM order create confusion.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Agentic actions
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Identify which actions an agent could safely help with.&lt;/li&gt;
&lt;li&gt;Separate low-risk actions from high-risk actions.&lt;/li&gt;
&lt;li&gt;Require explicit user confirmation for irreversible or sensitive actions.&lt;/li&gt;
&lt;li&gt;Make action labels precise.&lt;/li&gt;
&lt;li&gt;Consider whether structured tools, APIs, or WebMCP-like patterns could reduce ambiguity.&lt;/li&gt;
&lt;li&gt;Log and monitor agent-triggered flows differently from normal user flows when appropriate.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Search and generative AI visibility
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Keep foundational SEO healthy.&lt;/li&gt;
&lt;li&gt;Make important pages crawlable and indexable.&lt;/li&gt;
&lt;li&gt;Avoid manipulative “AI-only” content strategies.&lt;/li&gt;
&lt;li&gt;Use structured data where it genuinely helps, but do not treat it as a magic solution.&lt;/li&gt;
&lt;li&gt;Build content that deserves to be retrieved, summarized, and cited.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What I would test first
&lt;/h2&gt;

&lt;p&gt;I am still in the first-look and reading-the-docs phase with WebMCP, so I do not want to pretend I have shipped it in production.&lt;/p&gt;

&lt;p&gt;But if I were testing this today, I would start with a simple local service website or a booking flow.&lt;/p&gt;

&lt;p&gt;For example, a small hotel or B&amp;amp;B website.&lt;/p&gt;

&lt;p&gt;The task would be:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can an AI agent understand the available rooms, compare the options, identify cancellation rules, check dates, and prepare a booking request without guessing?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I would audit the site in five passes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx6tkno41p6gs2xcexl71.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx6tkno41p6gs2xcexl71.png" alt="Five-pass agent readiness audit workflow showing Content, HTML, Accessibility, Search, and Action review steps used to evaluate whether a website is ready to be understood and used by AI agents." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Content pass:&lt;/strong&gt; Are room types, prices, policies, services, accessibility options, and location details explicit?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTML pass:&lt;/strong&gt; Are headings, links, buttons, forms, and labels semantic?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accessibility pass:&lt;/strong&gt; Does the accessibility tree communicate the same intent as the visual UI?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search pass&lt;/strong&gt;: Are key pages crawlable, indexable, and structured enough for discovery? &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Action pass:&lt;/strong&gt; Which actions could safely be delegated to an agent, and which must require human approval?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That kind of audit is not just useful for agents.&lt;/p&gt;

&lt;p&gt;It improves the site for everyone.&lt;/p&gt;

&lt;p&gt;A clearer room page helps search engines.&lt;/p&gt;

&lt;p&gt;A better form helps users.&lt;/p&gt;

&lt;p&gt;A stronger accessibility tree helps assistive technologies.&lt;/p&gt;

&lt;p&gt;A more explicit policy helps customers trust the business.&lt;/p&gt;

&lt;p&gt;And if agents become a common interface to the web, the same improvements make the website easier for agents to operate.&lt;/p&gt;

&lt;p&gt;That is why I think agent readiness is not a speculative idea.&lt;/p&gt;

&lt;p&gt;It is a practical engineering direction.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where I disagree with the hype
&lt;/h2&gt;

&lt;p&gt;I do not think every website needs to rush into agentic automation tomorrow.&lt;/p&gt;

&lt;p&gt;I also do not think every form needs to become a tool, every page needs an AI-specific representation, or every business needs to rebuild its interface around agents.&lt;/p&gt;

&lt;p&gt;That would be premature.&lt;/p&gt;

&lt;p&gt;There is also a risk that “agent-ready” becomes the next buzzword used to sell shallow checklists.&lt;/p&gt;

&lt;p&gt;We should avoid that.&lt;/p&gt;

&lt;p&gt;The best version of agent readiness is not hype.&lt;/p&gt;

&lt;p&gt;It is not tricking AI systems.&lt;/p&gt;

&lt;p&gt;It is not replacing human UX.&lt;/p&gt;

&lt;p&gt;It is not publishing hundreds of pages for every possible query variation.&lt;/p&gt;

&lt;p&gt;It is the disciplined work of making websites easier to understand, verify, and operate.&lt;/p&gt;

&lt;p&gt;That work is boring in the best possible way.&lt;/p&gt;

&lt;p&gt;It is labels.&lt;/p&gt;

&lt;p&gt;It is headings.&lt;/p&gt;

&lt;p&gt;It is clear content.&lt;/p&gt;

&lt;p&gt;It is stable layouts.&lt;/p&gt;

&lt;p&gt;It is accessible components.&lt;/p&gt;

&lt;p&gt;It is explicit actions.&lt;/p&gt;

&lt;p&gt;It is careful confirmation flows.&lt;/p&gt;

&lt;p&gt;It is technical structure.&lt;/p&gt;

&lt;p&gt;And that is exactly why developers should care.&lt;/p&gt;




&lt;h2&gt;
  
  
  The opportunity for developers
&lt;/h2&gt;

&lt;p&gt;The developers who understand this shift early will have an advantage.&lt;/p&gt;

&lt;p&gt;Not because they will chase every new AI feature.&lt;/p&gt;

&lt;p&gt;But because they will build websites that are robust across interaction modes.&lt;/p&gt;

&lt;p&gt;A good agent-ready website works for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a human reading on mobile;&lt;/li&gt;
&lt;li&gt;a keyboard user navigating forms;&lt;/li&gt;
&lt;li&gt;a screen reader user exploring controls;&lt;/li&gt;
&lt;li&gt;a crawler discovering content;&lt;/li&gt;
&lt;li&gt;a search system retrieving information;&lt;/li&gt;
&lt;li&gt;an AI assistant summarizing options;&lt;/li&gt;
&lt;li&gt;a browser agent trying to complete a delegated task.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is a powerful design constraint.&lt;/p&gt;

&lt;p&gt;It brings together disciplines that are often treated separately:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SEO;&lt;/li&gt;
&lt;li&gt;accessibility;&lt;/li&gt;
&lt;li&gt;UX writing;&lt;/li&gt;
&lt;li&gt;frontend architecture;&lt;/li&gt;
&lt;li&gt;structured data;&lt;/li&gt;
&lt;li&gt;performance;&lt;/li&gt;
&lt;li&gt;content strategy;&lt;/li&gt;
&lt;li&gt;AI product design.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Google I/O 2026 made this convergence much more visible.&lt;/p&gt;

&lt;p&gt;The web is not becoming less important because of AI.&lt;/p&gt;

&lt;p&gt;The web is becoming the environment where agents must prove they can act usefully, safely, and reliably.&lt;/p&gt;

&lt;p&gt;That makes the quality of websites more important, not less.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;Google I/O 2026 made me think less about rankings and more about readiness.&lt;/p&gt;

&lt;p&gt;Ranking is about being selected.&lt;/p&gt;

&lt;p&gt;Citation is about being trusted.&lt;/p&gt;

&lt;p&gt;Agent readiness is about being understood well enough to be used.&lt;/p&gt;

&lt;p&gt;That is the missing layer.&lt;/p&gt;

&lt;p&gt;The next web will not be won only by pages that look beautiful or rank well.&lt;/p&gt;

&lt;p&gt;It will be won by pages that humans can trust, search systems can retrieve, and agents can operate without ambiguity.&lt;/p&gt;

&lt;p&gt;For developers, that is both a challenge and an opportunity.&lt;/p&gt;

&lt;p&gt;We do not only need better AI models.&lt;/p&gt;

&lt;p&gt;We need better websites for AI to work with.&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.google/innovation-and-ai/technology/developers-tools/google-io-2026-collection/" rel="noopener noreferrer"&gt;Google I/O 2026: News and announcements
&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.chrome.com/blog/chrome-at-io26" rel="noopener noreferrer"&gt;15 updates from Google I/O 2026: Powering the agentic web with new capabilities, tools, and features in Chrome
&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.chrome.com/blog/webmcp-epp" rel="noopener noreferrer"&gt;WebMCP is available for early preview
&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.dev/articles/ai-agent-site-ux" rel="noopener noreferrer"&gt;Build agent-friendly websites
&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/search/docs/fundamentals/ai-optimization-guide" rel="noopener noreferrer"&gt;Optimizing your website for generative AI features on Google Search
&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/search/blog/2026/05/a-new-resource-for-optimizing" rel="noopener noreferrer"&gt;A new resource for optimizing for generative AI in Google Search
&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.chrome.com/docs/lighthouse/agentic-browsing/webmcp-schema-validity" rel="noopener noreferrer"&gt;Lighthouse: WebMCP schema validity&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  AI assistance disclosure
&lt;/h2&gt;

&lt;p&gt;I used AI assistance to help organize research notes, refine the outline, and improve editorial clarity. The final angle, opinions, examples, and publishing decisions are mine.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>googleiochallenge</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Why Your Brand Is Invisible to AI Search (It's Not About Rankings)</title>
      <dc:creator>Juan Camilo Auriti</dc:creator>
      <pubDate>Fri, 15 May 2026 15:58:22 +0000</pubDate>
      <link>https://dev.to/juanauriti/why-your-brand-is-invisible-to-ai-search-its-not-about-rankings-56pd</link>
      <guid>https://dev.to/juanauriti/why-your-brand-is-invisible-to-ai-search-its-not-about-rankings-56pd</guid>
      <description>&lt;p&gt;A site can rank well on Google and still fail to appear in AI-generated answers. I keep seeing this pattern during visibility checks: strong organic presence, useful content, and weak or inconsistent citation across ChatGPT, Perplexity, Gemini, or Google's AI Overviews for comparable queries.&lt;/p&gt;

&lt;p&gt;This is not a ranking problem. It's a visibility gap of a different kind, and fixing it requires a different approach.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key Takeaways&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Google rankings and AI search visibility are distinct systems with different citation logic&lt;/li&gt;
&lt;li&gt;In AI search contexts, answer density often appears to matter more than traditional ranking signals alone&lt;/li&gt;
&lt;li&gt;Explicit author attribution, structured content, and factual precision are practical GEO signals that traditional SEO tools usually don't measure&lt;/li&gt;
&lt;li&gt;Citation behavior varies meaningfully across platforms: what works for Perplexity doesn't always transfer to ChatGPT or Gemini&lt;/li&gt;
&lt;li&gt;Fixing AI invisibility requires a content audit targeting GEO signals, not an SEO ranking fix&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The AI Search Visibility Gap Nobody's Measuring
&lt;/h2&gt;

&lt;p&gt;Consider this scenario: a technical agency has spent two years building topical authority in their space. Top 3 rankings for their core keywords. A content library with solid E-E-A-T signals. Good backlink profile. By traditional metrics, they're doing the right things.&lt;/p&gt;

&lt;p&gt;Then someone on their team starts checking AI search. They ask ChatGPT to recommend tools in their category. Then Perplexity. Then Google AI Overviews. The agency doesn't appear in any of them, not once across 15 queries covering topics they rank #1 for.&lt;/p&gt;

&lt;p&gt;That's not a fringe case. It's a pattern I keep encountering when I start running visibility checks.&lt;/p&gt;

&lt;p&gt;The reason it goes unnoticed is that traditional analytics don't capture it. Google Search Console doesn't report AI answer impressions. GA4 doesn't segment "traffic from AI citation." The gap is real but invisible to the dashboards most practitioners live in.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why the systems are different
&lt;/h3&gt;

&lt;p&gt;Google's search ranking logic and LLM citation logic have different optimization targets.&lt;/p&gt;

&lt;p&gt;Google's ranking algorithm weights domain authority, backlinks, freshness, and relevance signals. It's a retrieval-and-ranking system designed to surface the most authoritative source for a query.&lt;/p&gt;

&lt;p&gt;LLMs work differently. When generating an answer, a model doesn't browse the web in real-time (except in specific configurations like Perplexity's). It draws on its training data and, in search-integrated systems, on retrieved passages that match the query context. The retrieval step favors content that directly answers the question, content where the relevant information is dense, clearly structured, and attributed.&lt;/p&gt;

&lt;p&gt;High domain authority helps at the margins, but it doesn't determine whether your specific content gets extracted. That's determined by whether the passage itself is answer-dense, factually precise, and structurally parseable.&lt;/p&gt;




&lt;h2&gt;
  
  
  What AI Search Actually Optimizes For
&lt;/h2&gt;

&lt;p&gt;When I look at pages that consistently get cited versus pages that don't, the patterns that show up aren't about rankings. They're about a few specific content properties.&lt;/p&gt;

&lt;h3&gt;
  
  
  Answer density
&lt;/h3&gt;

&lt;p&gt;The simplest way to describe answer density: how much of the user's likely question does this content resolve in a single chunk?&lt;/p&gt;

&lt;p&gt;AI systems using retrieval-augmented generation (RAG) can evaluate content at passage level, not only page level. A 3,000-word article that buries the answer in paragraph 12 may be less useful for citation than a shorter page that resolves the query clearly in the first few sections, even if the longer article performs well in traditional search.&lt;/p&gt;

&lt;p&gt;This is where SEO and GEO (generative engine optimization) diverge most sharply. Google's algorithm often rewards comprehensive coverage and depth. AI retrieval rewards directness and concentration. Content optimized for one isn't automatically optimized for the other.&lt;/p&gt;

&lt;p&gt;The fix isn't to write shorter content. It's to restructure so each major section opens with a direct answer to the question that section implies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Structural clarity
&lt;/h3&gt;

&lt;p&gt;LLMs extract passages more reliably when the content is clearly organized. Headers that state what the reader is about to learn, not decorative headers, but functional ones, help retrieval systems understand where relevant content is.&lt;/p&gt;

&lt;p&gt;Definition-first patterns work well. When introducing a technical term or concept, state the definition before unpacking it. "Answer density is the ratio of directly answerable content to total content in a given passage" is a better passage start than "To understand how content performs in AI retrieval, we need to think about the relationship between what users ask and what content delivers."&lt;/p&gt;

&lt;p&gt;FAQ sections in Q&amp;amp;A format are reliable citation targets because they mirror the structure of how queries are posed. An H3 that says "How does citation quality scoring work?" followed immediately by a direct answer is the kind of passage LLMs extract cleanly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Entity clarity
&lt;/h3&gt;

&lt;p&gt;This one is underestimated. LLMs weight content differently depending on whether the source and context are clearly established.&lt;/p&gt;

&lt;p&gt;Author attribution matters. In my observations across queries over several months, pages with visible author bylines and structured author metadata appear more frequently in AI-generated answers than comparable pages without them. I don't have access to the retrieval weighting logic, but the pattern is consistent enough to treat author attribution as a working GEO signal.&lt;/p&gt;

&lt;p&gt;Beyond authorship, entity clarity means: is it clear what organization this is, what topic this covers, and when it was written? Publication dates in frontmatter, clear organizational affiliation, and explicit topic statements in the first paragraph all contribute.&lt;/p&gt;

&lt;p&gt;Google's AI Overviews documentation discusses E-E-A-T in relation to search quality. Perplexity surfaces sources prominently, which makes author attribution, provenance, and content clarity easier to inspect during practical visibility checks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Factual precision
&lt;/h3&gt;

&lt;p&gt;LLMs are trained to avoid reproducing claims that are vague, unsupported, or potentially inaccurate. Content full of "studies show," "experts agree," and vague statistics without attribution is treated with caution by retrieval systems, and appropriately so.&lt;/p&gt;

&lt;p&gt;Precision helps in two ways. First, specific claims are more extractable because they're more query-relevant ("citation rate increased 40% after adding author schema" matches a retrieval query better than "adding schema can help"). Second, attributed claims reduce the risk that the LLM generates a hallucinated version, if the claim is specific and sourced, there's less ambiguity.&lt;/p&gt;

&lt;p&gt;The pattern I keep seeing: content that reads like a practitioner sharing observable evidence tends to be easier to evaluate than content that reads like a generic summary of general knowledge. The former has specific claims, hedged appropriately, with clear provenance. The latter looks like it could have been generated by the LLM itself.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Rankings Don't Transfer
&lt;/h2&gt;

&lt;p&gt;A common response when I describe this gap is: "But we rank well, doesn't that mean AI trusts us?"&lt;/p&gt;

&lt;p&gt;Partially. High-authority domains do appear more frequently in AI answers. But authority doesn't guarantee citation, and it doesn't determine &lt;em&gt;which content&lt;/em&gt; from a domain gets cited. I've seen cases where a high-authority domain is cited for a secondary, less-optimized post while the primary pillar page, well-ranked, well-linked, gets no citation at all.&lt;/p&gt;

&lt;p&gt;The reason is that passage relevance, not page authority, drives which content gets extracted. A high-DA domain with answer-dense, well-structured content will outperform its rankings in AI search. A high-DA domain with authority-heavy but answer-poor content will underperform.&lt;/p&gt;

&lt;p&gt;There's also a platform divergence that complicates the picture. Perplexity, ChatGPT (when web-browsing), and Google AI Overviews have meaningfully different citation behaviors.&lt;/p&gt;

&lt;p&gt;Across practical visibility checks, citation behavior can vary meaningfully by platform. Perplexity often makes source selection easier to inspect because citations are surfaced prominently. ChatGPT with browsing may combine known authorities with practitioner sources when the query requires recent or specific context. Google AI Overviews appears more closely tied to Google's broader search quality and source evaluation systems.&lt;/p&gt;

&lt;p&gt;Same content, same domain, different citation behavior depending on where you query. Optimizing for one platform's behavior without understanding the others leaves significant visibility gaps.&lt;/p&gt;




&lt;h2&gt;
  
  
  What a GEO Audit Catches That a Standard SEO Audit Won't
&lt;/h2&gt;

&lt;p&gt;A standard SEO audit checks keyword density, heading structure, page speed, internal links, meta elements. These are necessary for rankings. They're not sufficient for AI search visibility.&lt;/p&gt;

&lt;p&gt;A GEO-focused audit covers different signals:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Answer density per section&lt;/strong&gt;, Is each major H2 section self-contained enough to answer the question it implies? Can the section be extracted and stand alone as a useful passage?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Entity markup&lt;/strong&gt;, Is &lt;code&gt;Article&lt;/code&gt; or &lt;code&gt;BlogPosting&lt;/code&gt; schema implemented with &lt;code&gt;author&lt;/code&gt;, &lt;code&gt;datePublished&lt;/code&gt;, and &lt;code&gt;organization&lt;/code&gt;? Is the author a named individual or an anonymous entity?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Factual precision scan&lt;/strong&gt;, Are there vague, unattributed claims that an LLM would flag as potentially inaccurate? "Many studies show" vs. "according to Google's 2024 AI Overviews documentation" is a significant difference in citation safety.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Citation structure&lt;/strong&gt;, Are claims linked to sources? Is the content internally consistent? Does it avoid making absolute claims in areas where platform behavior is known to vary?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Structural patterns for LLM retrieval&lt;/strong&gt;, Are there definition-first passages, Q&amp;amp;A sections, comparison tables? These structures extract cleanly from retrieval systems.&lt;/p&gt;

&lt;p&gt;This is the gap I've been building GeoReady to address. GeoReady's GEO audit workflow analyzes pages against these signals: answer density, entity clarity, factual precision, structural clarity, and schema implementation. The output is designed to show which signals are present, which are missing, and which specific changes could improve citation quality.&lt;/p&gt;

&lt;p&gt;The audit layer is running and producing data across test pages. I'm finding consistent patterns: pages that resolve the query density requirement and have clear author attribution score meaningfully higher on citation quality metrics than pages with equivalent Google rankings but poor GEO signal coverage.&lt;/p&gt;




&lt;h2&gt;
  
  
  The AI Search Visibility Monitoring Problem
&lt;/h2&gt;

&lt;p&gt;Even if you fix your content's GEO signals, AI search visibility isn't stable. Citation behavior shifts with model updates, with changes in how retrieval systems weight sources, and with the competitive landscape as more content optimizes for AI retrieval.&lt;/p&gt;

&lt;p&gt;I've been running answer snapshot tests, capturing AI-generated answers for target queries at regular intervals, and the citation drift is real. A source that appears in Perplexity's answers for a query in January may not appear in April. Sometimes this is because the source's content changed. Often it's because other sources got better, or because the model update shifted retrieval weighting.&lt;/p&gt;

&lt;p&gt;Traditional SEO tooling tracks ranking changes over time. There's no equivalent mainstream infrastructure for tracking AI search citation changes with the same maturity. This is one of the things I'm working on with GeoReady's answer snapshot monitoring workflow: capturing citations over time so drift becomes easier to detect.&lt;/p&gt;

&lt;p&gt;The practical implication: treating AI visibility as a one-time fix is wrong. It's a continuous monitoring problem, same as ranking monitoring, just for a different system.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where to Start
&lt;/h2&gt;

&lt;p&gt;If you want to diagnose your AI visibility gap, the sequence I'd suggest:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Run visibility checks across platforms.&lt;/strong&gt; Query ChatGPT, Perplexity, and Google AI Overviews with the exact queries your target audience uses. Note which competitors appear and which of your pages, if any, get cited. Do this for 10-15 queries across your core topics.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Identify the citation pattern.&lt;/strong&gt; Are there any of your pages that do get cited? What do they have in common structurally? This gives you a baseline for what's already working.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Audit your highest-value content for GEO signals.&lt;/strong&gt; Focus on answer density and entity clarity first, these are the highest-impact signals I've found and they're directly actionable. Does each section lead with a direct answer? Is author attribution visible and marked up in schema?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prioritize factual precision on pages making strong claims.&lt;/strong&gt; Review pages with statistics, recommendations, or comparative claims. Add attribution where it's missing. Qualify claims that are really observations rather than established facts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Track your changes.&lt;/strong&gt; After making GEO improvements to a set of pages, run visibility checks again in four to six weeks. Note whether citation frequency changes. This is rough monitoring, but it gives you directional signal on what's working.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The underlying logic is the same as traditional SEO, understand the system's optimization target, align your content with it, measure the results. The system just has different optimization targets than Google's ranking algorithm.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Means for SEO Practice
&lt;/h2&gt;

&lt;p&gt;AI search isn't replacing Google search. The traffic data doesn't support that, at least not yet. But AI-generated answers are increasingly the first touchpoint for queries across many categories, and the practitioners who build AI visibility now are building something that doesn't yet show up in standard analytics.&lt;/p&gt;

&lt;p&gt;The good news is that GEO-optimized content is also good content by most reasonable standards. High answer density, clear structure, factual precision, explicit authorship, these make content better for human readers too. There's no adversarial tradeoff between optimizing for AI retrieval and optimizing for human engagement.&lt;/p&gt;

&lt;p&gt;The bad news is that the tooling to measure and track AI search visibility is still catching up to the problem. Most SEO dashboards can't tell you whether you're appearing in AI answers, which queries are driving AI citations, or whether your citation rate is improving or declining over time.&lt;/p&gt;

&lt;p&gt;That's the gap I'm focused on closing. The patterns are legible enough to act on. The data collection problem is solvable. And the practitioners who treat AI visibility as a separate, measurable problem, rather than assuming rankings transfer, will have a significant head start.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Juan Camilo Auriti is building &lt;a href="https://geoready.dev" rel="noopener noreferrer"&gt;GeoReady&lt;/a&gt;, a set of tools for GEO audits, AI search visibility monitoring, and citation quality analysis. He writes about AI search, GEO methodology, and what practical visibility checks reveal.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>seo</category>
      <category>ai</category>
      <category>webdev</category>
      <category>marketing</category>
    </item>
    <item>
      <title>GEO Optimizer v4.10.0: AI Search Audits Need Signals, Not Checklists</title>
      <dc:creator>Juan Camilo Auriti</dc:creator>
      <pubDate>Thu, 30 Apr 2026 06:32:49 +0000</pubDate>
      <link>https://dev.to/juanauriti/geo-optimizer-v4100-ai-search-audits-need-signals-not-checklists-4i10</link>
      <guid>https://dev.to/juanauriti/geo-optimizer-v4100-ai-search-audits-need-signals-not-checklists-4i10</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8u6mig8wj693juanhth0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8u6mig8wj693juanhth0.png" alt="Geo Optimizer v4.10.0 hero image - Not enough to be found, you need to be cited" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I released &lt;strong&gt;GEO Optimizer v4.10.0&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The codename for this release is &lt;strong&gt;Veil&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Not because this version adds one huge visible feature, but because it focuses on something more important for the long-term direction of the project: making hidden signals easier to detect, classify, and reason about.&lt;/p&gt;

&lt;p&gt;GEO Optimizer started as a simple idea:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can we audit whether a website is technically and semantically ready to be cited by AI search engines?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That question looks simple.&lt;/p&gt;

&lt;p&gt;It is not.&lt;/p&gt;

&lt;p&gt;Classic SEO tooling is mostly built around crawling, indexing, ranking, metadata, schema, performance, content structure, links, and discoverability in traditional search results.&lt;/p&gt;

&lt;p&gt;Those things still matter.&lt;/p&gt;

&lt;p&gt;But AI search adds a different layer.&lt;/p&gt;

&lt;p&gt;A page does not only need to be found.&lt;/p&gt;

&lt;p&gt;It needs to be understood.&lt;/p&gt;

&lt;p&gt;It needs to be trusted.&lt;/p&gt;

&lt;p&gt;It needs to be extractable.&lt;/p&gt;

&lt;p&gt;It needs to be citeable.&lt;/p&gt;

&lt;p&gt;It needs to survive summarization without losing meaning.&lt;/p&gt;

&lt;p&gt;It needs to expose enough structure for an AI system to decide, among many possible sources, that this page is worth using in a generated answer.&lt;/p&gt;

&lt;p&gt;That is the problem GEO Optimizer is trying to measure.&lt;/p&gt;

&lt;p&gt;Not perfectly.&lt;/p&gt;

&lt;p&gt;Not magically.&lt;/p&gt;

&lt;p&gt;But technically, iteratively, and in the open.&lt;/p&gt;

&lt;h2&gt;
  
  
  What GEO Optimizer is
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg2mtkdng7f8b8h6o2gtl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg2mtkdng7f8b8h6o2gtl.png" alt="Found vs cited - classic search visibility compared to AI citation readiness" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GEO Optimizer is an open-source Python toolkit for &lt;strong&gt;Generative Engine Optimization&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The goal is to audit, optimize, and monitor whether a website is ready for AI search systems such as ChatGPT, Perplexity, Claude, Gemini, and similar answer engines.&lt;/p&gt;

&lt;p&gt;You can run it from the CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--upgrade&lt;/span&gt; geo-optimizer-skill
geo audit &lt;span class="nt"&gt;--url&lt;/span&gt; https://example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can audit a sitemap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;geo audit &lt;span class="nt"&gt;--sitemap&lt;/span&gt; https://example.com/sitemap.xml &lt;span class="nt"&gt;--max-urls&lt;/span&gt; 25
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can compare before and after versions of a page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;geo diff &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--before&lt;/span&gt; https://example.com/page-old &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--after&lt;/span&gt; https://example.com/page-new
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can track changes over time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;geo audit &lt;span class="nt"&gt;--url&lt;/span&gt; https://example.com &lt;span class="nt"&gt;--save-history&lt;/span&gt; &lt;span class="nt"&gt;--regression&lt;/span&gt;
geo &lt;span class="nb"&gt;history&lt;/span&gt; &lt;span class="nt"&gt;--url&lt;/span&gt; https://example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also generate files and structures that help AI discovery:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;geo llms &lt;span class="nt"&gt;--base-url&lt;/span&gt; https://example.com &lt;span class="nt"&gt;--output&lt;/span&gt; ./public/llms.txt
geo schema &lt;span class="nt"&gt;--type&lt;/span&gt; faq &lt;span class="nt"&gt;--url&lt;/span&gt; https://example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The basic workflow is intentionally simple.&lt;/p&gt;

&lt;p&gt;Give GEO Optimizer a URL.&lt;/p&gt;

&lt;p&gt;It returns a score, findings, and recommendations.&lt;/p&gt;

&lt;p&gt;The larger goal, however, is not to create another vanity score.&lt;/p&gt;

&lt;p&gt;The goal is to build a practical audit layer for a new kind of visibility problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this release matters
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdjlcs663pn44rhkpc1d5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdjlcs663pn44rhkpc1d5.png" alt="What is new in GEO Optimizer v4.10.0 - release highlights" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;v4.10.0 is not about adding more checklist items.&lt;/p&gt;

&lt;p&gt;It is about refining the architecture of the signals.&lt;/p&gt;

&lt;p&gt;That distinction matters.&lt;/p&gt;

&lt;p&gt;A checklist asks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is there schema?&lt;/li&gt;
&lt;li&gt;Is there a title?&lt;/li&gt;
&lt;li&gt;Is there a meta description?&lt;/li&gt;
&lt;li&gt;Is there an &lt;code&gt;llms.txt&lt;/code&gt; file?&lt;/li&gt;
&lt;li&gt;Are AI crawlers blocked?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those questions are useful, but they are only the first layer.&lt;/p&gt;

&lt;p&gt;A signal-based audit asks deeper questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the content actually reliable enough to cite?&lt;/li&gt;
&lt;li&gt;Are claims supported or likely to trigger hallucinations?&lt;/li&gt;
&lt;li&gt;Does the page satisfy the intent of AI search queries?&lt;/li&gt;
&lt;li&gt;Is the site consistent across technical, semantic, and trust layers?&lt;/li&gt;
&lt;li&gt;Can the content be interpreted without forcing an AI system to guess?&lt;/li&gt;
&lt;li&gt;Can future audits compare signal movement over time?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the direction of v4.10.0.&lt;/p&gt;

&lt;p&gt;It is less about surface-level optimization and more about operationalizing AI search readiness.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Hallucination Bait Detection
&lt;/h2&gt;

&lt;p&gt;The first major addition in v4.10.0 is &lt;strong&gt;Hallucination Bait Detection&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is one of the areas I care about most.&lt;/p&gt;

&lt;p&gt;When people talk about GEO, they often focus on how to get cited by AI systems.&lt;/p&gt;

&lt;p&gt;That is only half of the problem.&lt;/p&gt;

&lt;p&gt;The other half is this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What happens when your content gives an AI system just enough information to sound confident, but not enough information to be accurate?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is hallucination bait.&lt;/p&gt;

&lt;p&gt;A page can unintentionally encourage bad AI output when it contains content patterns such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;unsourced statistics;&lt;/li&gt;
&lt;li&gt;absolute claims without evidence;&lt;/li&gt;
&lt;li&gt;speculative statements written as facts;&lt;/li&gt;
&lt;li&gt;vague numerical ranges;&lt;/li&gt;
&lt;li&gt;AI-generated language with no grounding;&lt;/li&gt;
&lt;li&gt;self-citations used as proof;&lt;/li&gt;
&lt;li&gt;missing units in measurements;&lt;/li&gt;
&lt;li&gt;sensitive claims without proper context or disclaimers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These patterns matter because AI search engines do not only retrieve text.&lt;/p&gt;

&lt;p&gt;They interpret it.&lt;/p&gt;

&lt;p&gt;They compress it.&lt;/p&gt;

&lt;p&gt;They combine it with other sources.&lt;/p&gt;

&lt;p&gt;They may cite it.&lt;/p&gt;

&lt;p&gt;If your page contains unsupported or ambiguous claims, the problem is not only that the page may rank poorly.&lt;/p&gt;

&lt;p&gt;The problem is that it may be cited incorrectly, summarized incorrectly, or avoided entirely because the trust signals are weak.&lt;/p&gt;

&lt;p&gt;In v4.10.0, GEO Optimizer detects eight hallucination-bait patterns and assigns severity levels.&lt;/p&gt;

&lt;p&gt;The goal is not to punish content.&lt;/p&gt;

&lt;p&gt;The goal is to make risky patterns visible before they become reputation problems.&lt;/p&gt;

&lt;p&gt;For example, consider this sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This method improves AI visibility by 300% for every website.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That sentence has multiple problems.&lt;/p&gt;

&lt;p&gt;It is absolute.&lt;/p&gt;

&lt;p&gt;It is unsourced.&lt;/p&gt;

&lt;p&gt;It uses a strong statistical claim.&lt;/p&gt;

&lt;p&gt;It generalizes across every website.&lt;/p&gt;

&lt;p&gt;It is exactly the kind of sentence that can look good in marketing copy and bad in an AI-generated answer.&lt;/p&gt;

&lt;p&gt;A better version would be:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In our internal tests on selected pages, this method improved citation readiness scores by up to 30%, depending on content structure, schema quality, and source clarity.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Still not perfect.&lt;/p&gt;

&lt;p&gt;But it is more bounded.&lt;/p&gt;

&lt;p&gt;More specific.&lt;/p&gt;

&lt;p&gt;Less likely to become hallucination bait.&lt;/p&gt;

&lt;p&gt;That is the type of distinction GEO tooling needs to surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. AI Search Intent Mapping
&lt;/h2&gt;

&lt;p&gt;The second important addition is &lt;strong&gt;AI Search Intent Mapping&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Traditional SEO already works with search intent.&lt;/p&gt;

&lt;p&gt;But AI search changes how intent is expressed and satisfied.&lt;/p&gt;

&lt;p&gt;A user does not always type a short query anymore.&lt;/p&gt;

&lt;p&gt;They may ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What is the best way to optimize a website so ChatGPT or Perplexity can cite it?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Or:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Compare the main tools for AI search visibility and explain which one is best for a developer workflow.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Or:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Give me a practical checklist to make my SaaS website more citeable by AI answer engines.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These are not just keywords.&lt;/p&gt;

&lt;p&gt;They are tasks.&lt;/p&gt;

&lt;p&gt;They combine research, comparison, recommendation, explanation, and action.&lt;/p&gt;

&lt;p&gt;v4.10.0 analyzes content across four intent categories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;informational;&lt;/li&gt;
&lt;li&gt;navigational;&lt;/li&gt;
&lt;li&gt;transactional;&lt;/li&gt;
&lt;li&gt;commercial.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The intent mapping works with pattern matching in both English and Italian.&lt;/p&gt;

&lt;p&gt;This matters because many websites are technically optimized, but they do not clearly satisfy the type of question an AI system is trying to answer.&lt;/p&gt;

&lt;p&gt;A page may be informative but not comparative.&lt;/p&gt;

&lt;p&gt;A page may be commercial but not trustworthy.&lt;/p&gt;

&lt;p&gt;A page may describe a product but fail to answer the actual buyer questions.&lt;/p&gt;

&lt;p&gt;A page may have strong copy but weak extraction points.&lt;/p&gt;

&lt;p&gt;For AI search, this is a problem.&lt;/p&gt;

&lt;p&gt;Generated answers often need to map a user query to source passages quickly.&lt;/p&gt;

&lt;p&gt;If your content does not expose its purpose clearly, the AI system has to infer too much.&lt;/p&gt;

&lt;p&gt;That increases ambiguity.&lt;/p&gt;

&lt;p&gt;And ambiguity is bad for citation.&lt;/p&gt;

&lt;p&gt;In practical terms, AI Search Intent Mapping helps answer questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What kind of AI search queries could this page satisfy?&lt;/li&gt;
&lt;li&gt;Which intent categories are underrepresented?&lt;/li&gt;
&lt;li&gt;Is the page useful for comparison-style queries?&lt;/li&gt;
&lt;li&gt;Does it provide enough direct answers?&lt;/li&gt;
&lt;li&gt;Does it contain actionable material or only generic positioning?&lt;/li&gt;
&lt;li&gt;Does the content match the likely prompt patterns users will write?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the bridge between content strategy and technical GEO auditing.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. HTTP retry with exponential backoff
&lt;/h2&gt;

&lt;p&gt;The third addition is less glamorous, but important: &lt;strong&gt;HTTP retry with exponential backoff&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Audit tools live or die by reliability.&lt;/p&gt;

&lt;p&gt;If a tool audits a website once and fails because of a transient timeout, a temporary 5xx error, a connection reset, or rate limiting, the result is not useful.&lt;/p&gt;

&lt;p&gt;In v4.10.0, &lt;code&gt;fetch_url()&lt;/code&gt; now retries transient failures with configurable attempts and backoff.&lt;/p&gt;

&lt;p&gt;That includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;timeouts;&lt;/li&gt;
&lt;li&gt;5xx responses;&lt;/li&gt;
&lt;li&gt;connection errors;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;429&lt;/code&gt; rate limiting.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not a flashy feature.&lt;/p&gt;

&lt;p&gt;But it matters for real usage.&lt;/p&gt;

&lt;p&gt;Especially when auditing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;client websites;&lt;/li&gt;
&lt;li&gt;production sites behind CDNs;&lt;/li&gt;
&lt;li&gt;large sitemaps;&lt;/li&gt;
&lt;li&gt;staging environments;&lt;/li&gt;
&lt;li&gt;slower CMS installations;&lt;/li&gt;
&lt;li&gt;sites with intermittent network behavior.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A serious audit tool cannot treat every temporary failure as a final truth.&lt;/p&gt;

&lt;p&gt;It needs to distinguish between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a real accessibility problem;&lt;/li&gt;
&lt;li&gt;a temporary network problem;&lt;/li&gt;
&lt;li&gt;a server that is rate limiting;&lt;/li&gt;
&lt;li&gt;a target that consistently fails.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That distinction becomes even more important as GEO Optimizer moves toward monitoring, tracking, and longitudinal analysis.&lt;/p&gt;

&lt;p&gt;If the audit history is noisy, the insight becomes noisy.&lt;/p&gt;

&lt;p&gt;Reliability is part of the signal architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Telemetry System
&lt;/h2&gt;

&lt;p&gt;v4.10.0 also introduces a structured telemetry system.&lt;/p&gt;

&lt;p&gt;This is local telemetry, backed by SQLite in the user environment.&lt;/p&gt;

&lt;p&gt;The goal is not surveillance.&lt;/p&gt;

&lt;p&gt;The goal is operational visibility.&lt;/p&gt;

&lt;p&gt;GEO Optimizer now tracks structured events with a &lt;code&gt;geo_&lt;/code&gt; prefix, including events such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;geo_audit_run&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;geo_score_improved&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;geo_suggestion_applied&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;geo_api_error&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;geo_badge_generated&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This matters because audits are not one-time artifacts.&lt;/p&gt;

&lt;p&gt;A single audit tells you where you are now.&lt;/p&gt;

&lt;p&gt;Telemetry helps you understand movement.&lt;/p&gt;

&lt;p&gt;What changed?&lt;/p&gt;

&lt;p&gt;Which suggestions were applied?&lt;/p&gt;

&lt;p&gt;Did the score improve?&lt;/p&gt;

&lt;p&gt;Did errors increase?&lt;/p&gt;

&lt;p&gt;Are badges being generated?&lt;/p&gt;

&lt;p&gt;Are audits being repeated over time?&lt;/p&gt;

&lt;p&gt;This is the foundation for more serious workflows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;dashboards;&lt;/li&gt;
&lt;li&gt;regression detection;&lt;/li&gt;
&lt;li&gt;client reporting;&lt;/li&gt;
&lt;li&gt;CI/CD quality gates;&lt;/li&gt;
&lt;li&gt;historical visibility tracking;&lt;/li&gt;
&lt;li&gt;release impact analysis;&lt;/li&gt;
&lt;li&gt;product analytics for the audit engine itself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GEO is becoming less of a content trick and more of an operations problem.&lt;/p&gt;

&lt;p&gt;Telemetry is part of that transition.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I am focusing on signals instead of checklists
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0p4zy9ccvm5b98w36sk3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0p4zy9ccvm5b98w36sk3.png" alt="Signals not checklists - deeper AI search readiness model" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The easy version of GEO is a checklist.&lt;/p&gt;

&lt;p&gt;Add &lt;code&gt;llms.txt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Add schema.&lt;/p&gt;

&lt;p&gt;Allow AI crawlers.&lt;/p&gt;

&lt;p&gt;Write clearer headings.&lt;/p&gt;

&lt;p&gt;Add sources.&lt;/p&gt;

&lt;p&gt;Use statistics.&lt;/p&gt;

&lt;p&gt;Create FAQ sections.&lt;/p&gt;

&lt;p&gt;All of that can help.&lt;/p&gt;

&lt;p&gt;But if GEO stops there, it becomes shallow very quickly.&lt;/p&gt;

&lt;p&gt;A website can pass a checklist and still be weak for AI citation.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it can have schema, but poor entity consistency;&lt;/li&gt;
&lt;li&gt;it can have citations, but cite low-quality or circular sources;&lt;/li&gt;
&lt;li&gt;it can have long content, but no extractable definitions;&lt;/li&gt;
&lt;li&gt;it can allow AI crawlers, but block important assets through CDN behavior;&lt;/li&gt;
&lt;li&gt;it can publish many pages, but expose no clear topical authority;&lt;/li&gt;
&lt;li&gt;it can use strong claims, but provide no verification path;&lt;/li&gt;
&lt;li&gt;it can rank well in classic search, but be hard to summarize reliably.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why I am moving GEO Optimizer toward signal architecture.&lt;/p&gt;

&lt;p&gt;A good GEO audit should not only ask whether something exists.&lt;/p&gt;

&lt;p&gt;It should ask whether that thing contributes to citation readiness.&lt;/p&gt;

&lt;p&gt;A title tag exists.&lt;/p&gt;

&lt;p&gt;But does it clarify the page?&lt;/p&gt;

&lt;p&gt;Schema exists.&lt;/p&gt;

&lt;p&gt;But does it describe the right entity?&lt;/p&gt;

&lt;p&gt;Sources exist.&lt;/p&gt;

&lt;p&gt;But are they useful, traceable, and relevant?&lt;/p&gt;

&lt;p&gt;Statistics exist.&lt;/p&gt;

&lt;p&gt;But are they grounded?&lt;/p&gt;

&lt;p&gt;The page has content.&lt;/p&gt;

&lt;p&gt;But can a model extract a safe answer from it?&lt;/p&gt;

&lt;p&gt;This is the difference between compliance and usefulness.&lt;/p&gt;

&lt;p&gt;And AI search rewards usefulness much more than mechanical compliance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example workflow
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdtbcwkt7lw57ter80fzy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdtbcwkt7lw57ter80fzy.png" alt="GEO Optimizer workflow - audit, improve, compare, monitor" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A simple workflow looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--upgrade&lt;/span&gt; geo-optimizer-skill
geo audit &lt;span class="nt"&gt;--url&lt;/span&gt; https://example.com &lt;span class="nt"&gt;--format&lt;/span&gt; rich
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then inspect the weak areas.&lt;/p&gt;

&lt;p&gt;If the score is low because the site blocks AI crawlers, fix &lt;code&gt;robots.txt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If the score is low because schema is missing, add structured data.&lt;/p&gt;

&lt;p&gt;If citability is low, improve definitions, source clarity, statistics, and content structure.&lt;/p&gt;

&lt;p&gt;If hallucination bait appears, rewrite risky claims.&lt;/p&gt;

&lt;p&gt;If intent mapping is weak, restructure the page around the questions users actually ask in AI search systems.&lt;/p&gt;

&lt;p&gt;Then run the audit again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;geo audit &lt;span class="nt"&gt;--url&lt;/span&gt; https://example.com &lt;span class="nt"&gt;--save-history&lt;/span&gt; &lt;span class="nt"&gt;--regression&lt;/span&gt;
geo &lt;span class="nb"&gt;history&lt;/span&gt; &lt;span class="nt"&gt;--url&lt;/span&gt; https://example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the workflow I want GEO Optimizer to support:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;audit the current state;&lt;/li&gt;
&lt;li&gt;identify weak signals;&lt;/li&gt;
&lt;li&gt;apply changes;&lt;/li&gt;
&lt;li&gt;compare results;&lt;/li&gt;
&lt;li&gt;monitor over time;&lt;/li&gt;
&lt;li&gt;catch regressions before visibility drops.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Tests and reliability
&lt;/h2&gt;

&lt;p&gt;This release also continues the focus on test coverage and mocked execution.&lt;/p&gt;

&lt;p&gt;v4.10.0 ships with &lt;strong&gt;1425 tests&lt;/strong&gt;, all mocked, with zero network access during the test suite.&lt;/p&gt;

&lt;p&gt;That is important for this kind of tool.&lt;/p&gt;

&lt;p&gt;A GEO audit engine touches many unstable surfaces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;remote websites;&lt;/li&gt;
&lt;li&gt;HTML parsing;&lt;/li&gt;
&lt;li&gt;schema extraction;&lt;/li&gt;
&lt;li&gt;robots rules;&lt;/li&gt;
&lt;li&gt;HTTP behavior;&lt;/li&gt;
&lt;li&gt;content heuristics;&lt;/li&gt;
&lt;li&gt;scoring thresholds;&lt;/li&gt;
&lt;li&gt;output formats;&lt;/li&gt;
&lt;li&gt;local history;&lt;/li&gt;
&lt;li&gt;telemetry;&lt;/li&gt;
&lt;li&gt;API and CLI boundaries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If tests depend on real websites, the test suite becomes unreliable.&lt;/p&gt;

&lt;p&gt;A site changes and your test breaks.&lt;/p&gt;

&lt;p&gt;A server times out and your CI fails.&lt;/p&gt;

&lt;p&gt;A third-party page updates its markup and your expected output is no longer valid.&lt;/p&gt;

&lt;p&gt;Mocked tests keep the engine deterministic.&lt;/p&gt;

&lt;p&gt;For an audit tool, determinism is not a luxury.&lt;/p&gt;

&lt;p&gt;It is part of trust.&lt;/p&gt;

&lt;p&gt;The current CI path is also moving through &lt;code&gt;ruff&lt;/code&gt; and gradual &lt;code&gt;mypy&lt;/code&gt;, because the project is becoming large enough that type boundaries and static checks are no longer optional niceties.&lt;/p&gt;

&lt;p&gt;They are maintenance infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means for the roadmap
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxa7pbdgeu5z2xl15726o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxa7pbdgeu5z2xl15726o.png" alt="GEO Optimizer roadmap - from CLI audit tool to AI search visibility ecosystem" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means for the roadmap
&lt;/h2&gt;

&lt;p&gt;The public roadmap for GEO Optimizer is now more deliberate.&lt;/p&gt;

&lt;p&gt;I do not want to ship noisy releases just to make the repository look active.&lt;/p&gt;

&lt;p&gt;I want focused waves.&lt;/p&gt;

&lt;p&gt;v4.10.0 is the signal refinement release.&lt;/p&gt;

&lt;p&gt;The next direction is broader:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;better retrieval surface analysis;&lt;/li&gt;
&lt;li&gt;deeper scoring recalibration;&lt;/li&gt;
&lt;li&gt;stronger structural pattern recognition;&lt;/li&gt;
&lt;li&gt;clearer reporting;&lt;/li&gt;
&lt;li&gt;better web experience;&lt;/li&gt;
&lt;li&gt;more useful monitoring;&lt;/li&gt;
&lt;li&gt;more reliable citation-quality analysis;&lt;/li&gt;
&lt;li&gt;stronger factual accuracy checks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The long-term goal is to move GEO Optimizer from a CLI audit tool into a broader ecosystem for AI search visibility.&lt;/p&gt;

&lt;p&gt;That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CLI for developers;&lt;/li&gt;
&lt;li&gt;web version for faster testing;&lt;/li&gt;
&lt;li&gt;reports for teams and clients;&lt;/li&gt;
&lt;li&gt;monitoring for change detection;&lt;/li&gt;
&lt;li&gt;MCP integration for AI-assisted workflows;&lt;/li&gt;
&lt;li&gt;structured outputs for CI/CD and automation;&lt;/li&gt;
&lt;li&gt;open-source core for transparency.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I still think the CLI matters.&lt;/p&gt;

&lt;p&gt;A lot.&lt;/p&gt;

&lt;p&gt;Developers need local, scriptable, inspectable tools.&lt;/p&gt;

&lt;p&gt;But not every useful GEO workflow should require living in the terminal.&lt;/p&gt;

&lt;p&gt;That is why the web layer matters too.&lt;/p&gt;

&lt;p&gt;The CLI gives control.&lt;/p&gt;

&lt;p&gt;The web version gives accessibility.&lt;/p&gt;

&lt;p&gt;The open-source engine keeps the system inspectable.&lt;/p&gt;

&lt;h2&gt;
  
  
  GEO is not “SEO is dead”
&lt;/h2&gt;

&lt;p&gt;I do not believe SEO is dead.&lt;/p&gt;

&lt;p&gt;I think SEO is becoming more layered.&lt;/p&gt;

&lt;p&gt;Traditional search still matters.&lt;/p&gt;

&lt;p&gt;Technical SEO still matters.&lt;/p&gt;

&lt;p&gt;Content quality still matters.&lt;/p&gt;

&lt;p&gt;Schema still matters.&lt;/p&gt;

&lt;p&gt;Crawlability still matters.&lt;/p&gt;

&lt;p&gt;But AI search introduces new questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can this page be safely summarized?&lt;/li&gt;
&lt;li&gt;Can this claim be verified?&lt;/li&gt;
&lt;li&gt;Is the entity behind the content clear?&lt;/li&gt;
&lt;li&gt;Does the page expose useful extraction points?&lt;/li&gt;
&lt;li&gt;Would an AI answer engine cite this source?&lt;/li&gt;
&lt;li&gt;Would it cite it accurately?&lt;/li&gt;
&lt;li&gt;Would it choose a competitor instead?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the layer I am building for.&lt;/p&gt;

&lt;p&gt;The future of organic visibility will not be only about being found.&lt;/p&gt;

&lt;p&gt;It will also be about being understood, trusted, and cited.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Install or upgrade:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--upgrade&lt;/span&gt; geo-optimizer-skill
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run an audit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;geo audit &lt;span class="nt"&gt;--url&lt;/span&gt; https://example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Try a richer output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;geo audit &lt;span class="nt"&gt;--url&lt;/span&gt; https://example.com &lt;span class="nt"&gt;--format&lt;/span&gt; rich
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Explore the project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/Auriti-Labs/geo-optimizer-skill" rel="noopener noreferrer"&gt;https://github.com/Auriti-Labs/geo-optimizer-skill&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Web demo: &lt;a href="https://geoready.dev" rel="noopener noreferrer"&gt;https://geoready.dev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Documentation: &lt;a href="https://auriti-labs.github.io/geo-optimizer-skill/" rel="noopener noreferrer"&gt;https://auriti-labs.github.io/geo-optimizer-skill/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Release v4.10.0: &lt;a href="https://github.com/Auriti-Labs/geo-optimizer-skill/releases/tag/v4.10.0" rel="noopener noreferrer"&gt;https://github.com/Auriti-Labs/geo-optimizer-skill/releases/tag/v4.10.0&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you try it on your own site, I would be especially interested in feedback around:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;hallucination bait findings;&lt;/li&gt;
&lt;li&gt;intent mapping quality;&lt;/li&gt;
&lt;li&gt;false positives;&lt;/li&gt;
&lt;li&gt;confusing recommendations;&lt;/li&gt;
&lt;li&gt;missing GEO signals;&lt;/li&gt;
&lt;li&gt;reporting clarity;&lt;/li&gt;
&lt;li&gt;CI/CD use cases;&lt;/li&gt;
&lt;li&gt;web version usability.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GEO is still early.&lt;/p&gt;

&lt;p&gt;That is exactly why open tools matter.&lt;/p&gt;

&lt;p&gt;We need fewer vague claims about “AI visibility” and more inspectable systems that developers, founders, SEO specialists, and content teams can actually test.&lt;/p&gt;

&lt;p&gt;That is what I am trying to build with GEO Optimizer.&lt;/p&gt;

&lt;p&gt;v4.10.0 is not the final destination.&lt;/p&gt;

&lt;p&gt;It is another step toward a more measurable, technical, and transparent approach to AI search visibility.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>programming</category>
    </item>
    <item>
      <title>How I Use Multiple AI Models in Real Workflows</title>
      <dc:creator>Juan Camilo Auriti</dc:creator>
      <pubDate>Tue, 28 Apr 2026 12:10:00 +0000</pubDate>
      <link>https://dev.to/juanauriti/how-i-use-multiple-ai-models-in-real-workflows-1kkh</link>
      <guid>https://dev.to/juanauriti/how-i-use-multiple-ai-models-in-real-workflows-1kkh</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;One model for everything is usually the wrong strategy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpzyf3ze2aur05zwhojwy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpzyf3ze2aur05zwhojwy.png" alt="Dark premium tech visual showing multiple AI systems connected in one workflow with the title One Model Isn't The Strategy. Workflow Is." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A lot of people still ask:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which AI model is the best?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After months of daily use, I think that question misses the real opportunity.&lt;/p&gt;

&lt;p&gt;The better question is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which model is best for this specific task?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That shift changed how I work, build, write, and ship projects faster.&lt;/p&gt;

&lt;p&gt;Instead of relying on one tool, I now use multiple AI models in a practical workflow.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why One Model Usually Isn’t Enough
&lt;/h2&gt;

&lt;p&gt;Different models have different strengths.&lt;/p&gt;

&lt;p&gt;Some are better at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reasoning deeply&lt;/li&gt;
&lt;li&gt;planning systems&lt;/li&gt;
&lt;li&gt;writing code&lt;/li&gt;
&lt;li&gt;moving fast&lt;/li&gt;
&lt;li&gt;summarizing large inputs&lt;/li&gt;
&lt;li&gt;generating creative angles&lt;/li&gt;
&lt;li&gt;running locally for privacy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Trying to force one model to do everything often creates friction.&lt;/p&gt;

&lt;p&gt;You either lose quality, speed, or flexibility.&lt;/p&gt;




&lt;h2&gt;
  
  
  My Current Multi-Model Approach
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffmmfpt8l60drc6bnjcxf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffmmfpt8l60drc6bnjcxf.png" alt="Modern infographic showing different AI models connected to tasks like reasoning, coding, writing, research and privacy workflows." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I choose models based on the job.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Deep Thinking / Architecture
&lt;/h3&gt;

&lt;p&gt;When I need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;strategy&lt;/li&gt;
&lt;li&gt;technical planning&lt;/li&gt;
&lt;li&gt;tradeoff analysis&lt;/li&gt;
&lt;li&gt;system design&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I use stronger reasoning models.&lt;/p&gt;

&lt;p&gt;These are slower, but worth it for important decisions.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Coding / Iteration Speed
&lt;/h3&gt;

&lt;p&gt;When I need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;component drafts&lt;/li&gt;
&lt;li&gt;bug fixing&lt;/li&gt;
&lt;li&gt;repetitive coding&lt;/li&gt;
&lt;li&gt;quick iterations&lt;/li&gt;
&lt;li&gt;code suggestions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I use faster coding-oriented models.&lt;/p&gt;

&lt;p&gt;Speed matters here.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Writing / Content Structuring
&lt;/h3&gt;

&lt;p&gt;When I need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;article outlines&lt;/li&gt;
&lt;li&gt;title ideas&lt;/li&gt;
&lt;li&gt;rewriting messy notes&lt;/li&gt;
&lt;li&gt;organizing thoughts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I use models that are fast and clean with language.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Local Private Tasks
&lt;/h3&gt;

&lt;p&gt;When I need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;privacy&lt;/li&gt;
&lt;li&gt;offline use&lt;/li&gt;
&lt;li&gt;experimentation&lt;/li&gt;
&lt;li&gt;low-cost repetitive tasks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I use local models.&lt;/p&gt;

&lt;p&gt;They are often underrated.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Real Upgrade: Workflows
&lt;/h2&gt;

&lt;p&gt;The biggest productivity jump did not come from a better prompt.&lt;/p&gt;

&lt;p&gt;It came from chaining tools together.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm2p1obzd0ql68w8xlrjy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm2p1obzd0ql68w8xlrjy.png" alt="Premium dark infographic comparing prompts with scalable AI workflows including research, draft, build, review and ship stages." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;h3&gt;
  
  
  Content Workflow
&lt;/h3&gt;

&lt;p&gt;Idea → Outline → Draft → Improve → Publish → Repurpose&lt;/p&gt;

&lt;h3&gt;
  
  
  Dev Workflow
&lt;/h3&gt;

&lt;p&gt;Problem → Analyze → Plan → Code → Review → Refactor&lt;/p&gt;

&lt;h3&gt;
  
  
  Research Workflow
&lt;/h3&gt;

&lt;p&gt;Question → Compare Sources → Summarize → Decide&lt;/p&gt;

&lt;p&gt;That is where leverage appears.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example: How I Build Faster
&lt;/h2&gt;

&lt;p&gt;When working on a project, I often do something like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use one model to analyze the task&lt;/li&gt;
&lt;li&gt;Use another to generate implementation options&lt;/li&gt;
&lt;li&gt;Use a coding model for execution&lt;/li&gt;
&lt;li&gt;Use a stronger model for review&lt;/li&gt;
&lt;li&gt;Save repeatable patterns for future tasks&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is much stronger than chatting with one assistant for hours.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Most People Still Get Wrong
&lt;/h2&gt;

&lt;p&gt;Many users still treat AI as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a search engine&lt;/li&gt;
&lt;li&gt;a toy&lt;/li&gt;
&lt;li&gt;a rewrite tool&lt;/li&gt;
&lt;li&gt;a novelty app&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That leaves a lot of value unused.&lt;/p&gt;

&lt;p&gt;The bigger opportunity is treating AI like a modular production system.&lt;/p&gt;




&lt;h2&gt;
  
  
  My Rules for Using Multiple Models
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Match the tool to the task
&lt;/h3&gt;

&lt;p&gt;Do not use one hammer for every problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Use expensive intelligence selectively
&lt;/h3&gt;

&lt;p&gt;Reserve stronger models for higher-value decisions.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Use fast models for execution loops
&lt;/h3&gt;

&lt;p&gt;Speed compounds.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Keep human judgment central
&lt;/h3&gt;

&lt;p&gt;Models generate options. You decide.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Save successful workflows
&lt;/h3&gt;

&lt;p&gt;If something works twice, systemize it.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Means for Developers
&lt;/h2&gt;

&lt;p&gt;For developers, builders, and operators, AI usage is becoming a skill layer.&lt;/p&gt;

&lt;p&gt;Not just prompt skill.&lt;/p&gt;

&lt;p&gt;Operational skill.&lt;/p&gt;

&lt;p&gt;The advantage is shifting toward people who know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;when to use which model&lt;/li&gt;
&lt;li&gt;how to combine tools&lt;/li&gt;
&lt;li&gt;how to remove friction&lt;/li&gt;
&lt;li&gt;how to systemize wins&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkw40wrequa548ssmffvr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkw40wrequa548ssmffvr.png" alt="Dark futuristic visual with the phrase The Future Belongs To People Who Orchestrate AI Well and connected system nodes." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;The future may not belong to people using the smartest model.&lt;/p&gt;

&lt;p&gt;It may belong to people using multiple models intelligently.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>developers</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
