<?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: Jonas</title>
    <description>The latest articles on DEV Community by Jonas (@jonas_bra).</description>
    <link>https://dev.to/jonas_bra</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%2F4119849%2F9431f070-74c1-4610-99bf-ea407307c6fe.png</url>
      <title>DEV Community: Jonas</title>
      <link>https://dev.to/jonas_bra</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jonas_bra"/>
    <language>en</language>
    <item>
      <title>Don't Count Failed AI Visibility Checks as Brand Misses</title>
      <dc:creator>Jonas</dc:creator>
      <pubDate>Wed, 21 Oct 2026 11:59:03 +0000</pubDate>
      <link>https://dev.to/jonas_bra/dont-count-failed-ai-visibility-checks-as-brand-misses-50f5</link>
      <guid>https://dev.to/jonas_bra/dont-count-failed-ai-visibility-checks-as-brand-misses-50f5</guid>
      <description>&lt;p&gt;&lt;em&gt;Cover: AI-generated editorial illustration.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;An AI-answer tracker schedules eight checks. Five finish, two fail, and one is still pending. Two of the completed answers mention the configured brand. What percentage should the dashboard display?&lt;/p&gt;

&lt;p&gt;This is a synthetic example. It exposes a reporting bug that is easy to introduce: dividing mentions by every row, including rows that never produced a usable answer.&lt;/p&gt;

&lt;p&gt;Two out of eight is 25%. But that number treats three missing results as if they were completed answers that omitted the brand. Two out of five is 40% of the completed, classified answers. That still needs a coverage label: five of eight checks completed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate collection state from answer classification
&lt;/h2&gt;

&lt;p&gt;A request failing tells you something about the collection process. It does not tell you whether the brand would have appeared. Keep that state separate from the result of inspecting a successful answer.&lt;/p&gt;

&lt;p&gt;There is another unknown to preserve. A request may complete while classification is unavailable or inconclusive. That answer should not silently become a negative result either.&lt;/p&gt;

&lt;p&gt;For this example, completed means an answer was collected successfully. The mentioned field is true, false, or unclassified. Mentioned means only that the configured brand was identified in that answer; it does not mean recommended or cited.&lt;/p&gt;

&lt;h2&gt;
  
  
  Aggregate only the records that support the metric
&lt;/h2&gt;

&lt;p&gt;The function below expects validated input with one final record per logical check. Resolve retries and duplicate records before calling it. Its recorded count is the number of rows supplied, not an independently verified schedule size.&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;summarize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;completed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;completed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;classified&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mentioned&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;boolean&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mentions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;classified&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mentioned&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;recorded&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;classified&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;classified&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;mentions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;misses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;classified&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="nx"&gt;mentions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;unclassified&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;completed&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="nx"&gt;classified&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;failed&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="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pending&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="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;mentionRate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;classified&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;mentions&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;classified&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The boolean guard is intentional. A missing field, null, or the string false cannot supply a trustworthy negative classification. With no classified answers, the rate is unavailable rather than zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  Show the denominator beside the percentage
&lt;/h2&gt;

&lt;p&gt;For the eight-record example, the summary is: 8 recorded, 5 completed, 5 classified, 2 mentions, 3 misses, 2 failed, 1 pending, and a mention rate of 0.4.&lt;/p&gt;

&lt;p&gt;A useful label is “2 of 5 classified answers mentioned the brand.” Put “5 of 8 checks completed” beside it. If some completed answers are unclassified, show that count too.&lt;/p&gt;

&lt;p&gt;For a separately configured schedule, compare the collected records with the expected logical check IDs. A record missing from the database is not automatically a pending check, and this function does not discover missing scheduled jobs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the cases that can make a chart lie
&lt;/h2&gt;

&lt;p&gt;The included function was run against the eight-record example and three boundary cases: an empty collection returns an unavailable rate; a failed row carrying mentioned: true does not increase mentions; and a completed row carrying mentioned: null remains unclassified with an unavailable rate.&lt;/p&gt;

&lt;p&gt;Also verify retry handling in the system that produces these records. Counting three attempts at the same question as three separate scheduled observations changes the denominator before the reporting function ever runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep interpretation separate from arithmetic
&lt;/h2&gt;

&lt;p&gt;A correct percentage does not describe every person's AI experience. It describes the answers in the selected collection. Keep the question set, provider, dates and known request conditions available so a reader can judge what was measured.&lt;/p&gt;

&lt;p&gt;If a later run has lower completion or a different question set, display that context before presenting the change as a visibility trend. Fixing an aggregation bug improves the report; it does not change the underlying answers.&lt;/p&gt;

&lt;p&gt;AI disclosure: This article was drafted with an AI assistant. The examples use synthetic data and were checked by running the included JavaScript. They are not production measurements or customer results.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>testing</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>JavaScript Links and AI Crawlers: Make Navigation Discoverable</title>
      <dc:creator>Jonas</dc:creator>
      <pubDate>Wed, 07 Oct 2026 10:28:23 +0000</pubDate>
      <link>https://dev.to/jonas_bra/javascript-links-and-ai-crawlers-make-navigation-discoverable-2io</link>
      <guid>https://dev.to/jonas_bra/javascript-links-and-ai-crawlers-make-navigation-discoverable-2io</guid>
      <description>&lt;p&gt;&lt;em&gt;Cover: AI-generated editorial illustration.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A pricing card can work perfectly when you click it and still expose no link to the pricing page.&lt;/p&gt;

&lt;p&gt;That is the navigation problem I would check before changing how the whole app renders. The useful question is simple: where is the destination represented?&lt;/p&gt;

&lt;p&gt;My earlier &lt;a href="https://dev.to/jonas_bra/raw-html-vs-rendered-html-what-ai-crawlers-actually-see-4l40"&gt;raw HTML versus rendered HTML article&lt;/a&gt; covered the different documents a crawler might receive. This article stays with one part of that problem: whether important pages are connected by usable links.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inspect the HTML behind the component
&lt;/h2&gt;

&lt;p&gt;A component called ServiceCard might produce this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/services/maintenance&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  Website maintenance
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The button can run JavaScript and change the route. The markup does not expose that destination as a link.&lt;/p&gt;

&lt;p&gt;For navigation, the relevant output is:&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;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/services/maintenance"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Website maintenance&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can use your router's link component to preserve client-side navigation. Inspect the HTML it actually produces. The component name alone doesn't prove that an anchor and a usable href are present.&lt;/p&gt;

&lt;p&gt;Keep buttons for actions such as opening a dialog or applying a filter. Use a link when the intent is to take someone to another page.&lt;/p&gt;

&lt;p&gt;Google's &lt;a href="https://developers.google.com/search/docs/crawling-indexing/links-crawlable" rel="noopener noreferrer"&gt;crawlable-link guidance&lt;/a&gt; specifically recommends anchors with resolvable href values. It also explains that JavaScript-inserted links can be crawlable when they produce that markup. This is documented Google behavior, not a guarantee about every AI crawler.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check the destination before testing the click
&lt;/h2&gt;

&lt;p&gt;An anchor is only useful if its href identifies the intended destination.&lt;/p&gt;

&lt;p&gt;Look for empty values, a bare #, javascript: URLs or handlers that ignore the href and send the visitor somewhere else. A card can look like navigation while its only useful routing information lives inside an event handler.&lt;/p&gt;

&lt;p&gt;For the maintenance example, I would expect to find /services/maintenance in the anchor. Then I would copy that address and open it directly in a fresh tab.&lt;/p&gt;

&lt;p&gt;If the route works after entering through the homepage but fails when opened directly, investigate the production host and route handling. A successful in-app transition has hidden a separate delivery problem.&lt;/p&gt;

&lt;p&gt;Check the returned page as well. A 200 response containing the homepage is not evidence that the maintenance route works. The destination needs its own relevant heading and content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Find out when the link exists
&lt;/h2&gt;

&lt;p&gt;There are three useful checkpoints for the page containing the link:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The initial HTML response.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The rendered DOM before any interaction.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The DOM after opening a menu or clicking a control.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Search for the actual anchor and its href at each stage. A destination appearing inside a script string or JSON object is not the same as a navigation link.&lt;/p&gt;

&lt;p&gt;If the anchor is already in the initial response, you have established that it is available without running the app. If rendering adds it, its availability depends on the reader's rendering path. If it only appears after a click, discovery depends on that additional interaction too.&lt;/p&gt;

&lt;p&gt;This is a useful distinction for menus. A collapsed menu may already contain anchors in the document. Another menu may fetch and create them only when opened. They can look identical in the interface while presenting different navigation paths to an automated reader.&lt;/p&gt;

&lt;p&gt;Do not treat your browser test as proof of a particular AI crawler's behavior. It establishes what your page delivered in that test. Check the relevant service's documentation and available crawl evidence before making a crawler-specific claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  Audit a path through the site
&lt;/h2&gt;

&lt;p&gt;Checking the homepage alone leaves too much untested. Follow a real path a visitor needs, such as product page to integration guide to setup instructions.&lt;/p&gt;

&lt;p&gt;For each step, record the source page, the anchor's href and whether the destination loads directly. Also record whether the anchor appears before rendering, after rendering or only after interaction.&lt;/p&gt;

&lt;p&gt;That gives you a useful bug report: "The product card reaches the integration guide in-app, but it is a button with no anchor. The destination itself loads correctly." The repair can stay in the card component.&lt;/p&gt;

&lt;p&gt;A different finding needs a different fix: "The guide has a real link, but a direct request to the setup route returns the homepage." Rewriting the anchor text won't repair that route.&lt;/p&gt;

&lt;p&gt;The link graph matters too. A detailed guide can exist without a useful link from the page that explains the related product. Creating another guide would leave that missing connection in place. Add the relevant path and verify it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify the shared component in more than one place
&lt;/h2&gt;

&lt;p&gt;After a repair, check the header, cards and related-article navigation separately. They may look similar while using different components. Read the link text without the design around it. It should help someone understand where they are going.&lt;/p&gt;

&lt;p&gt;Vite itself does not decide whether your navigation becomes anchors, buttons or a rendered server document. Diagnose the deployed output and the route behavior.&lt;/p&gt;

&lt;p&gt;I would call this audit complete when the important paths expose real destinations, those routes load independently, and each destination contains the intended page. A rendering layer may help with a measured delivery gap, but it cannot infer a missing navigation relationship that the application never creates.&lt;/p&gt;

&lt;p&gt;This article was adapted from my earlier technical writing with AI assistance.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>seo</category>
      <category>a11y</category>
    </item>
    <item>
      <title>Raw HTML vs Rendered HTML: What AI Crawlers Actually See</title>
      <dc:creator>Jonas</dc:creator>
      <pubDate>Tue, 15 Sep 2026 18:48:51 +0000</pubDate>
      <link>https://dev.to/jonas_bra/raw-html-vs-rendered-html-what-ai-crawlers-actually-see-4l40</link>
      <guid>https://dev.to/jonas_bra/raw-html-vs-rendered-html-what-ai-crawlers-actually-see-4l40</guid>
      <description>&lt;p&gt;&lt;em&gt;Cover: AI-generated editorial illustration.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A web page can look complete in your browser and still send almost no useful content in its initial response.&lt;/p&gt;

&lt;p&gt;That sounds contradictory until you separate two things developers often treat as interchangeable: the HTML returned by the server and the document produced after a browser executes the page's JavaScript.&lt;/p&gt;

&lt;p&gt;For a person using a modern browser, the distinction is easy to miss. The browser runs the application, fetches data, updates the DOM, and presents the finished page.&lt;/p&gt;

&lt;p&gt;A crawler does not necessarily follow the same path. Some crawlers render JavaScript. Some render it later. Some use limited execution environments. Some primarily inspect the response they receive. A crawler may also stop when a script fails, a request is blocked, or rendering exceeds its resource budget.&lt;/p&gt;

&lt;p&gt;The useful question is therefore not just, “Does this page load?” It is, “Which version of this page is available to the system trying to read it?”&lt;/p&gt;

&lt;h2&gt;
  
  
  Three versions of the same page
&lt;/h2&gt;

&lt;p&gt;It helps to distinguish three representations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Initial or raw HTML&lt;/strong&gt; is the response body returned by the server before client-side JavaScript runs. You can inspect it with an HTTP client or your browser's “View Source” feature.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Browser-rendered DOM&lt;/strong&gt; is the document after the browser has parsed the response and executed the relevant JavaScript. This is what you normally inspect in the Elements panel of developer tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Crawler response&lt;/strong&gt; is what the server returns for a particular crawler request. It may match the initial HTML sent to a normal browser, but it does not have to. A site may route known crawlers to server-rendered or prerendered HTML while sending its client application to normal visitors.&lt;/p&gt;

&lt;p&gt;Consider a simplified client-rendered application:&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="cp"&gt;&amp;lt;!doctype html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Acme Analytics&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&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;"module"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/assets/app.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"root"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After JavaScript runs, the DOM might contain a product heading, several paragraphs, pricing links, documentation navigation, structured data, and customer questions. None of that information is present in the response above.&lt;/p&gt;

&lt;p&gt;The browser has enough information to build the page. A system reading only the response does not.&lt;/p&gt;

&lt;h2&gt;
  
  
  CSR and hydration are related, but not identical
&lt;/h2&gt;

&lt;p&gt;Client-side rendering (CSR) usually means that JavaScript creates most of the meaningful document in the browser. The server sends a shell and the client fetches or computes the content.&lt;/p&gt;

&lt;p&gt;Hydration starts from HTML that the server has already rendered. JavaScript attaches behavior to that existing markup. A hydrated page can therefore expose substantial content before JavaScript runs—provided the server response really contains that content.&lt;/p&gt;

&lt;p&gt;Framework choice alone does not tell you which result a crawler receives. React, Vue, Svelte, Angular, and similar tools can participate in client rendering, server rendering, static generation, or combinations of them. What matters is the deployed response for a specific URL.&lt;/p&gt;

&lt;p&gt;This is why statements such as “React sites are invisible to crawlers” are not useful. Some React sites return complete documents. Others return a thin shell. Many fall somewhere in between.&lt;/p&gt;

&lt;h2&gt;
  
  
  What can disappear from the raw response?
&lt;/h2&gt;

&lt;p&gt;When the initial HTML is thin, the missing material is not limited to body copy. Common gaps include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The primary heading and descriptive text&lt;/li&gt;
&lt;li&gt;Product or article content loaded through an API&lt;/li&gt;
&lt;li&gt;Internal navigation and contextual links&lt;/li&gt;
&lt;li&gt;Canonical, description, or social metadata inserted by client code&lt;/li&gt;
&lt;li&gt;Structured data generated after mount&lt;/li&gt;
&lt;li&gt;Pagination or related-content links&lt;/li&gt;
&lt;li&gt;Text inside tabs, accordions, or client-side routes&lt;/li&gt;
&lt;li&gt;Error and empty states that replace expected content when a request fails&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each gap has a different consequence. Missing body text reduces what a non-rendering reader can understand. Missing internal links affects discovery. Missing structured data removes an explicit machine-readable description, even when visible copy remains available.&lt;/p&gt;

&lt;p&gt;A character-count difference alone cannot tell you whether a page has a serious problem. A cookie banner can add thousands of unimportant characters, while a smaller difference can contain the only product description or links to deeper pages. The comparison needs both numbers and inspection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Search crawlers and AI crawlers are not one audience
&lt;/h2&gt;

&lt;p&gt;It is tempting to divide the world into “Googlebot” and “AI bots,” but both groups contain systems with different purposes and capabilities.&lt;/p&gt;

&lt;p&gt;A search engine may fetch a document, schedule rendering separately, revisit it with another service, and combine the result with other signals. An AI company may operate different crawlers for search indexing, model training, user-requested retrieval, and link previews. An assistant answering a live question may also use a retrieval system that behaves differently from the company's general web crawler.&lt;/p&gt;

&lt;p&gt;Do not assume every crawler executes the same JavaScript—or that one successful render proves availability to all the others. Make the important public meaning available in useful HTML, then verify crawler-specific behavior with evidence.&lt;/p&gt;

&lt;p&gt;This matters beyond traditional search because AI visibility has at least two separate layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Can a system access and understand the page?&lt;/li&gt;
&lt;li&gt;Does it choose to retrieve, cite, mention, or recommend that information for a particular question?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Improving the first layer does not guarantee the second. But a page that exposes no meaningful content creates an avoidable technical obstacle.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to compare the versions
&lt;/h2&gt;

&lt;p&gt;Start with the exact public URL, not a development route or a component in isolation.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Fetch the initial response
&lt;/h3&gt;

&lt;p&gt;Use an HTTP client and follow redirects deliberately:&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;-L&lt;/span&gt; https://example.com/product
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the body and inspect it as a document. Look for the page's main heading, a distinctive sentence, important links, canonical metadata, and structured data.&lt;/p&gt;

&lt;p&gt;Also record the final URL, status code, content type, redirect chain, and response size. A &lt;code&gt;200&lt;/code&gt; response only proves that the server returned something successfully; it does not prove that the response contains the page.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Inspect View Source
&lt;/h3&gt;

&lt;p&gt;“View Source” is a convenient representation of the server response. Do not confuse it with the Elements panel, which shows the live DOM after browser processing.&lt;/p&gt;

&lt;p&gt;Search the source for a sentence that visibly appears on the page. If it is absent, determine whether the browser adds it during rendering.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Disable JavaScript
&lt;/h3&gt;

&lt;p&gt;Loading the page without JavaScript is a useful diagnostic, although it is not a perfect crawler simulation. It quickly reveals whether meaningful HTML exists before the application starts.&lt;/p&gt;

&lt;p&gt;The expected result depends on the product. A complex editor may reasonably require JavaScript to function. Its public landing page should still be understandable without waiting for an application API.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Render in a controlled browser
&lt;/h3&gt;

&lt;p&gt;Use Playwright, Puppeteer, or another browser automation tool to capture the resulting DOM. Define what “finished” means: &lt;code&gt;load&lt;/code&gt; may be too early, while network idle may never arrive on a page with analytics or live connections.&lt;/p&gt;

&lt;p&gt;Compare meaningful signals rather than performing a byte-for-byte diff:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Normalized visible text&lt;/li&gt;
&lt;li&gt;Headings&lt;/li&gt;
&lt;li&gt;Links and destinations&lt;/li&gt;
&lt;li&gt;Title, canonical, and description&lt;/li&gt;
&lt;li&gt;Robots directives&lt;/li&gt;
&lt;li&gt;Structured-data blocks&lt;/li&gt;
&lt;li&gt;Final URL and HTTP behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Repeat with relevant user agents
&lt;/h3&gt;

&lt;p&gt;User-agent testing can reveal routing differences, but it must be interpreted carefully. A different response does not automatically mean that a crawler will execute or accept it. Conversely, an identical response does not show what the crawler later renders.&lt;/p&gt;

&lt;p&gt;Check what your own infrastructure does. Avoid claiming knowledge of a crawler's internal rendering process based only on a user-agent request.&lt;/p&gt;

&lt;h2&gt;
  
  
  When prerendering helps
&lt;/h2&gt;

&lt;p&gt;Prerendering can be useful when a public, content-oriented route sends a thin application shell and changing the application's rendering architecture is not immediately practical.&lt;/p&gt;

&lt;p&gt;A rendering service can execute the page, capture useful HTML, cache it, and serve that representation to eligible crawlers. This can be especially practical for an established SPA where a framework migration would be disproportionate to the problem.&lt;/p&gt;

&lt;p&gt;The output should remain current, preserve correct statuses and redirects, and represent the same public content a visitor receives. Stale or materially different content creates a new problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  When prerendering is unnecessary
&lt;/h2&gt;

&lt;p&gt;Do not add a rendering layer merely because the site uses JavaScript.&lt;/p&gt;

&lt;p&gt;You probably do not need it when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Server rendering or static generation already returns the important content&lt;/li&gt;
&lt;li&gt;The raw and rendered versions differ only in interactive behavior&lt;/li&gt;
&lt;li&gt;The route is private or application-only and is not intended for discovery&lt;/li&gt;
&lt;li&gt;The missing content can be fixed directly with a smaller server-side change&lt;/li&gt;
&lt;li&gt;The page's public metadata, text, links, and structured data are already present&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The best outcome is not “prerender everything.” It is “make each public page's important content reliably available with the least unnecessary complexity.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the response, not the framework label
&lt;/h2&gt;

&lt;p&gt;This raw-versus-rendered gap was one of the problems that led me to build Prerender Buddy. Its free checker compares the initial response, crawler-facing HTML, and browser-rendered page so the diagnosis is based on the URL's actual behavior rather than assumptions about its stack.&lt;/p&gt;

&lt;p&gt;You do not need a product to perform the basic investigation. &lt;code&gt;curl&lt;/code&gt;, browser source, developer tools, and a controlled browser script will take you a long way.&lt;/p&gt;

&lt;p&gt;The important habit is to stop treating “it loads in my browser” as the end of the test. A modern page is a sequence of representations. If discovery matters, inspect the representation that arrives before the interface becomes visible.&lt;/p&gt;

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