<?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: hey atlas</title>
    <description>The latest articles on DEV Community by hey atlas (@hey_atlas_5d684863e9b069a).</description>
    <link>https://dev.to/hey_atlas_5d684863e9b069a</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%2F3981862%2F1db3d7df-9450-4279-a943-f4289388945b.png</url>
      <title>DEV Community: hey atlas</title>
      <link>https://dev.to/hey_atlas_5d684863e9b069a</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hey_atlas_5d684863e9b069a"/>
    <language>en</language>
    <item>
      <title>I built a free one-file auto repair shop website template with a working booking widget (no React, no DB)</title>
      <dc:creator>hey atlas</dc:creator>
      <pubDate>Mon, 29 Jun 2026 00:26:11 +0000</pubDate>
      <link>https://dev.to/hey_atlas_5d684863e9b069a/i-built-a-free-one-file-auto-repair-shop-website-template-with-a-working-booking-widget-no-react-55i6</link>
      <guid>https://dev.to/hey_atlas_5d684863e9b069a/i-built-a-free-one-file-auto-repair-shop-website-template-with-a-working-booking-widget-no-react-55i6</guid>
      <description>&lt;p&gt;Local auto repair shops and mechanics lose real money to a bad website: a slow page, a phone number nobody answers during a job, and no way to book a service online. So I built one they can actually use, and I'm giving it away free.&lt;/p&gt;

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

&lt;p&gt;A &lt;strong&gt;single HTML file&lt;/strong&gt; auto repair / mechanic website template. No React, no build step, no database, no dependencies. You open one file, change a few lines of config at the top, and you have a fast, mobile-first garage website.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://aitoolsinsiderhq.com/free-auto-repair-website-template/live.html" rel="noopener noreferrer"&gt;https://aitoolsinsiderhq.com/free-auto-repair-website-template/live.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source + docs (MIT):&lt;/strong&gt; &lt;a href="https://github.com/atlashey-collab/auto-repair-website-template" rel="noopener noreferrer"&gt;https://github.com/atlashey-collab/auto-repair-website-template&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The part shops actually want: a working service-booking widget
&lt;/h2&gt;

&lt;p&gt;Most "free templates" are just a pretty landing page. This one has a real booking flow baked in, no backend required:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pick a service (oil change, brake job, diagnostics, tires, etc.)&lt;/li&gt;
&lt;li&gt;Type your car make and model&lt;/li&gt;
&lt;li&gt;Choose a day&lt;/li&gt;
&lt;li&gt;Choose a time&lt;/li&gt;
&lt;li&gt;Enter your name&lt;/li&gt;
&lt;li&gt;It sends the booking straight to the shop over WhatsApp&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because it hands the request off to WhatsApp, there is &lt;strong&gt;nothing to host and nothing to pay for&lt;/strong&gt; to receive bookings. The owner gets a clean message with the service, the car, and the time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why one file
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero hosting cost&lt;/strong&gt; — drop it on GitHub Pages, Netlify, or any static host.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edit in 5 minutes&lt;/strong&gt; — shop name, services, hours, phone, and colors all live in one config object at the top.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fast by default&lt;/strong&gt; — no framework, no render-blocking JS, valid &lt;code&gt;AutoRepair&lt;/code&gt; JSON-LD structured data so Google understands it's a local mechanic.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to use it
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Grab the file from the repo.&lt;/li&gt;
&lt;li&gt;Edit the config block (business name, services, hours, WhatsApp number).&lt;/li&gt;
&lt;li&gt;Push it to any static host.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's the whole thing. It's the 5th in a small series of free one-file local-business templates I've shipped (salon, restaurant, gym, dental, and now auto repair), all built on the same idea: a real booking widget, zero backend, zero cost.&lt;/p&gt;

&lt;p&gt;If you run a shop or build sites for local businesses, take it, fork it, ship it. Feedback and PRs welcome.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>html</category>
      <category>opensource</category>
      <category>css</category>
    </item>
    <item>
      <title>I built a free one-file dental clinic website template with a working appointment-booking widget (no React, no DB)</title>
      <dc:creator>hey atlas</dc:creator>
      <pubDate>Sun, 28 Jun 2026 23:21:12 +0000</pubDate>
      <link>https://dev.to/hey_atlas_5d684863e9b069a/i-built-a-free-one-file-dental-clinic-website-template-with-a-working-appointment-booking-widget-bdm</link>
      <guid>https://dev.to/hey_atlas_5d684863e9b069a/i-built-a-free-one-file-dental-clinic-website-template-with-a-working-appointment-booking-widget-bdm</guid>
      <description>&lt;p&gt;Small dental practices keep overpaying for slow, template-y sites, or they run on a bare booking link with no real website at all. So I built a free one I would actually hand to a clinic.&lt;/p&gt;

&lt;p&gt;It is a single HTML file. No React, no build step, no database, no monthly fees. Open it, edit the text, host it anywhere (GitHub Pages, Netlify, a plain folder).&lt;/p&gt;

&lt;h2&gt;
  
  
  What is inside
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A working &lt;strong&gt;Request an appointment&lt;/strong&gt; flow with a service picker (cleaning, whitening, implants, emergency)&lt;/li&gt;
&lt;li&gt;An honest, upfront &lt;strong&gt;pricing&lt;/strong&gt; section&lt;/li&gt;
&lt;li&gt;A calm, modern clinic design that holds up on mobile&lt;/li&gt;
&lt;li&gt;Clean, commented markup you can rip apart&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Live demo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://aitoolsinsiderhq.com/free-dental-website-template/live.html" rel="noopener noreferrer"&gt;https://aitoolsinsiderhq.com/free-dental-website-template/live.html&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Source (free, open)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/atlashey-collab/dental-website-template" rel="noopener noreferrer"&gt;https://github.com/atlashey-collab/dental-website-template&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the 4th in a series of free, bookable small-business templates (salon, restaurant, gym, and now dental). If you run a clinic, or you build sites for local businesses, take it, fork it, ship it.&lt;/p&gt;

&lt;p&gt;What would you add to the booking flow first, an insurance field or SMS confirmation? Curious what dentists actually want.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>html</category>
      <category>opensource</category>
      <category>css</category>
    </item>
    <item>
      <title>I built a free one-file gym &amp; fitness website template with a working free-trial booking widget (no React, no DB)</title>
      <dc:creator>hey atlas</dc:creator>
      <pubDate>Sun, 28 Jun 2026 01:25:26 +0000</pubDate>
      <link>https://dev.to/hey_atlas_5d684863e9b069a/i-built-a-free-one-file-gym-fitness-website-template-with-a-working-free-trial-booking-widget-no-1oa</link>
      <guid>https://dev.to/hey_atlas_5d684863e9b069a/i-built-a-free-one-file-gym-fitness-website-template-with-a-working-free-trial-booking-widget-no-1oa</guid>
      <description>&lt;p&gt;Most local gyms, fitness studios and personal trainers still have no real website. They live entirely inside Instagram and a phone number, and when someone wants to try a class they have to DM and wait.&lt;/p&gt;

&lt;p&gt;So I built a free, open-source template that fixes the one thing that actually matters for a gym: &lt;strong&gt;letting a stranger book a free trial class in a few taps, without any booking SaaS, database or monthly fee.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It is one &lt;code&gt;index.html&lt;/code&gt; file. No build step, no React, no backend.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;A clean boutique-gym landing page (hero, classes, schedule, trainers, pricing, gallery, map, hours).&lt;/li&gt;
&lt;li&gt;A working &lt;strong&gt;"book a free trial class" widget&lt;/strong&gt;: the visitor picks a class, a day and a time, types their name, and hits confirm.&lt;/li&gt;
&lt;li&gt;That confirm button opens a &lt;strong&gt;pre-filled WhatsApp message&lt;/strong&gt; to the gym's own number with the whole booking written out. The owner just replies to confirm. No Calendly, no Mindbody, no per-booking cut.&lt;/li&gt;
&lt;li&gt;A 3-tier membership pricing strip.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ExerciseGym&lt;/code&gt; JSON-LD structured data baked in for SEO.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why one file with no database
&lt;/h2&gt;

&lt;p&gt;For a single-location gym, a real booking backend is overkill. The owner already lives in WhatsApp. So instead of standing up auth + a DB + a calendar service, the booking is just a deep link:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Hi! I'd like to book a FREE TRIAL:
%0A• Class: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
%0A• Day: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;day&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
%0A• Time: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
%0A• Name: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://wa.me/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PHONE&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?text=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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 whole "form" is plain HTML + a few lines of vanilla JS that assemble that string. Zero dependencies, loads instantly, hosts free on GitHub Pages / Netlify / Vercel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make it yours
&lt;/h2&gt;

&lt;p&gt;Everything a gym needs to change lives in one config block at the top. Search the file for the word &lt;code&gt;EDIT&lt;/code&gt;, change the studio name, phone, classes, schedule and prices, and you are done. Swap three CSS color variables and the same template works for a yoga studio, a CrossFit box, a boxing gym or a PT business.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://aitoolsinsiderhq.com/gym-website-template/" rel="noopener noreferrer"&gt;https://aitoolsinsiderhq.com/gym-website-template/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source (MIT):&lt;/strong&gt; &lt;a href="https://github.com/atlashey-collab/gym-website-template" rel="noopener noreferrer"&gt;https://github.com/atlashey-collab/gym-website-template&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Free to use, fork and ship. If you run a gym or studio (or build sites for people who do), I would love feedback on the booking flow. This is the third in a small series of one-file vertical templates (salon and restaurant came first) — tell me which vertical you want next.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>html</category>
      <category>opensource</category>
      <category>css</category>
    </item>
    <item>
      <title>I built a free one-file restaurant website template with a working table-reservation widget (no React, no DB)</title>
      <dc:creator>hey atlas</dc:creator>
      <pubDate>Sun, 28 Jun 2026 00:38:29 +0000</pubDate>
      <link>https://dev.to/hey_atlas_5d684863e9b069a/i-built-a-free-one-file-restaurant-website-template-with-a-working-table-reservation-widget-no-dbi</link>
      <guid>https://dev.to/hey_atlas_5d684863e9b069a/i-built-a-free-one-file-restaurant-website-template-with-a-working-table-reservation-widget-no-dbi</guid>
      <description>&lt;p&gt;A while back I shipped a free one-file salon &amp;amp; barbershop site template here, and a few people asked which local-business vertical was next. Restaurants were the obvious one: most owners get quoted &lt;strong&gt;$1,500–$3,000&lt;/strong&gt; for a basic site, or get pushed onto a reservation platform that skims a fee off every single cover.&lt;/p&gt;

&lt;p&gt;So I built the same idea for restaurants — one &lt;code&gt;index.html&lt;/code&gt;, no build step, no framework, no database, no monthly fee — with a &lt;strong&gt;working table-reservation widget&lt;/strong&gt; that drops each booking straight into the owner's WhatsApp.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;▶ &lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://atlashey-collab.github.io/restaurant-website-template/" rel="noopener noreferrer"&gt;https://atlashey-collab.github.io/restaurant-website-template/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📦 &lt;strong&gt;Repo (MIT):&lt;/strong&gt; &lt;a href="https://github.com/atlashey-collab/restaurant-website-template" rel="noopener noreferrer"&gt;https://github.com/atlashey-collab/restaurant-website-template&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The constraint: one file, zero dependencies
&lt;/h2&gt;

&lt;p&gt;The whole site is a single &lt;code&gt;index.html&lt;/code&gt; you can open by double-clicking it. No &lt;code&gt;npm install&lt;/code&gt;, no bundler, no backend to deploy. A non-technical owner can edit their name, menu and hours in Notepad and drag the file onto GitHub Pages, Netlify or Vercel for free.&lt;/p&gt;

&lt;p&gt;That constraint is the whole point. The moment a restaurant site needs a build pipeline and a $20/mo host, the owner stops touching it and it rots. One file they actually understand beats a "real" stack they never log into again.&lt;/p&gt;

&lt;h2&gt;
  
  
  The reservation widget (the only interesting part)
&lt;/h2&gt;

&lt;p&gt;No backend, no Calendly embed. The guest picks &lt;strong&gt;party size → day → time → name&lt;/strong&gt;, and the confirm button builds a WhatsApp deep link with the booking pre-written:&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildReservation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hi &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;RESTAURANT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;brand&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;, I'd like to reserve a table for &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="o"&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;size&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; guests on &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&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;day&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; at &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&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;time&lt;/span&gt; &lt;span class="o"&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="o"&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;name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;RESTAURANT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hasWhatsApp&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://wa.me/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;RESTAURANT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;phone&lt;/span&gt; &lt;span class="o"&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="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&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;sms:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;RESTAURANT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;phone&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;?body=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&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 owner gets a normal WhatsApp message they reply to like any other. No subscription, no per-cover fee, nothing to administer. The available time slots come from a tiny &lt;code&gt;hours&lt;/code&gt; config (&lt;code&gt;[open, close]&lt;/code&gt; in 24h, &lt;code&gt;null&lt;/code&gt; = closed), so the widget never offers a slot when the place is shut.&lt;/p&gt;

&lt;h2&gt;
  
  
  SEO &amp;amp; AI-search, built in
&lt;/h2&gt;

&lt;p&gt;It ships with proper &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt;/meta tags and &lt;code&gt;Restaurant&lt;/code&gt; JSON-LD structured data, so Google rich results — and AI search like ChatGPT/Perplexity — can actually parse the name, cuisine, hours and address instead of guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Re-theme it for a cafe or pizzeria
&lt;/h2&gt;

&lt;p&gt;Theming is three CSS variables. Swap them and change the hero image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* cafe / bakery */&lt;/span&gt;     &lt;span class="nt"&gt;--olive&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;#a9763f&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="nt"&gt;--olive-deep&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#7&lt;/span&gt;&lt;span class="nt"&gt;c5326&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="nt"&gt;--forest&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#2&lt;/span&gt;&lt;span class="nt"&gt;a211a&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="c"&gt;/* pizzeria / trattoria */&lt;/span&gt; &lt;span class="nt"&gt;--olive&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;#c0392b&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="nt"&gt;--olive-deep&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#96281&lt;/span&gt;&lt;span class="nt"&gt;b&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="nt"&gt;--forest&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#23201&lt;/span&gt;&lt;span class="nt"&gt;b&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Take it
&lt;/h2&gt;

&lt;p&gt;It's MIT — fork it, ship it for a client, strip the credit, whatever. If you run a restaurant (or build sites for ones) and want a fast, honest starting point instead of a $2k quote, the &lt;a href="https://github.com/atlashey-collab/restaurant-website-template" rel="noopener noreferrer"&gt;repo is here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I'm slowly doing one of these per local-business vertical (salon and restaurant are live). If there's a vertical you'd want next — gym, dental clinic, café — say so in the comments.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>html</category>
      <category>opensource</category>
      <category>css</category>
    </item>
    <item>
      <title>I built a free one-file salon &amp; barbershop website with working online booking (no React, no DB)</title>
      <dc:creator>hey atlas</dc:creator>
      <pubDate>Sat, 27 Jun 2026 23:06:11 +0000</pubDate>
      <link>https://dev.to/hey_atlas_5d684863e9b069a/i-built-a-free-one-file-salon-barbershop-website-with-working-online-booking-no-react-no-db-3f4m</link>
      <guid>https://dev.to/hey_atlas_5d684863e9b069a/i-built-a-free-one-file-salon-barbershop-website-with-working-online-booking-no-react-no-db-3f4m</guid>
      <description>&lt;p&gt;Most salon owners get quoted &lt;strong&gt;$1,000 to $2,000&lt;/strong&gt; for a basic website, or pushed onto a $30/month booking app. That always struck me as absurd for what is, fundamentally, one page with a booking button. So I built the whole thing as a single &lt;code&gt;index.html&lt;/code&gt; file and open-sourced it (MIT).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://aitoolsinsiderhq.com/salon-website-template/live.html" rel="noopener noreferrer"&gt;Live demo&lt;/a&gt;&lt;/strong&gt; · &lt;strong&gt;&lt;a href="https://github.com/atlashey-collab/salon-website-template" rel="noopener noreferrer"&gt;Source on GitHub&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The constraints I gave myself
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One file.&lt;/strong&gt; No npm, no framework, no build step, no database. A salon owner with zero technical skills has to be able to edit it in Notepad and host it free.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A booking widget that actually works&lt;/strong&gt; with no backend and no paid platform.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEO and AI-search ready&lt;/strong&gt; out of the box (proper meta tags + &lt;code&gt;LocalBusiness&lt;/code&gt; JSON-LD), because the whole point is for the salon to get found.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The booking trick: WhatsApp as the backend
&lt;/h2&gt;

&lt;p&gt;The hard part with "no backend" is bookings. A booking platform wants a database and a monthly fee. But almost every salon already runs on WhatsApp. So the widget lets the client pick a service, day, time and name, then opens a pre-filled WhatsApp chat to the salon:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Hi Bloom Beauty Studio, I'd like to book: Hair Cut &amp;amp; Blow-Dry ($45) on Fri at 10:30 AM. Name: Jordan.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The salon confirms in the chat. No database, no per-booking fee, and it meets clients where they already are. The "already booked" striped slots are illustrative; you swap in a real calendar later if you grow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Everything is driven by one config block
&lt;/h2&gt;

&lt;p&gt;To make it editable by a non-coder, every business-specific value lives in a single object near the bottom of the file:&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="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;SALON&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;brand&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 Salon Name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;contact&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://wa.me/15551234567&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// your WhatsApp, digits only&lt;/span&gt;
  &lt;span class="na"&gt;hasWhatsApp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                      &lt;span class="c1"&gt;// false -&amp;gt; use a tel: number instead&lt;/span&gt;
  &lt;span class="na"&gt;services&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hair Cut &amp;amp; Blow-Dry&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$45&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Balayage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;            &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$160&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="na"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;                                &lt;span class="c1"&gt;// [open, close] 24h, null = closed&lt;/span&gt;
    &lt;span class="na"&gt;Sun&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="na"&gt;Mon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="na"&gt;Tue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="na"&gt;Wed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;Thu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="na"&gt;Fri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="na"&gt;Sat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;17&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change those values, double-click the file to preview, and drop it on GitHub Pages / Netlify / Vercel. Live in about five minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make it a barbershop
&lt;/h2&gt;

&lt;p&gt;Swap three CSS variables and the service names:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* barbershop palette */&lt;/span&gt;
&lt;span class="nt"&gt;--rose&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;#c8a24a&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="nt"&gt;--rose-deep&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;#a8842f&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="nt"&gt;--plum&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="err"&gt;#23201&lt;/span&gt;&lt;span class="nt"&gt;b&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;…then change the hero image and the services (Fade, Beard Trim, Hot Towel Shave). Same engine, different vibe. It also works for nail bars, beauty salons, and lash/brow studios.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's in the box
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Mobile-first responsive layout&lt;/li&gt;
&lt;li&gt;Services &amp;amp; prices grid, gallery, Google reviews, opening hours, Maps embed&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;LocalBusiness&lt;/code&gt; structured data so Google and ChatGPT/Perplexity understand the business&lt;/li&gt;
&lt;li&gt;Zero dependencies, loads in under a second&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's MIT licensed, so use it for client work, fork it, ship it. If you run a salon and would rather not touch any code, the &lt;a href="https://aitoolsinsiderhq.com/salon-website-template/" rel="noopener noreferrer"&gt;landing page&lt;/a&gt; explains a done-for-you option too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/atlashey-collab/salon-website-template" rel="noopener noreferrer"&gt;https://github.com/atlashey-collab/salon-website-template&lt;/a&gt; — a star is appreciated if it's useful. Happy to answer questions about the no-backend booking approach in the comments.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>html</category>
      <category>showdev</category>
      <category>css</category>
    </item>
    <item>
      <title>I built a GitHub Action that fails CI when your meta tags are broken</title>
      <dc:creator>hey atlas</dc:creator>
      <pubDate>Fri, 26 Jun 2026 18:05:45 +0000</pubDate>
      <link>https://dev.to/hey_atlas_5d684863e9b069a/i-built-a-github-action-that-fails-ci-when-your-meta-tags-are-broken-3e9h</link>
      <guid>https://dev.to/hey_atlas_5d684863e9b069a/i-built-a-github-action-that-fails-ci-when-your-meta-tags-are-broken-3e9h</guid>
      <description>&lt;p&gt;Google retired the Structured Data Testing Tool. X killed its Card Validator. Facebook's debugger is behind a login. So in 2026 there is no fast way to catch a busted &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; or a missing &lt;code&gt;og:image&lt;/code&gt; &lt;em&gt;before&lt;/em&gt; it ships to production. You find out when the page is live and the LinkedIn share renders as a grey box.&lt;/p&gt;

&lt;p&gt;I kept shipping that exact bug, so I put the check in CI. It's a zero-dependency GitHub Action.&lt;/p&gt;

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

&lt;p&gt;It lints the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; of a page — a local HTML file &lt;strong&gt;or&lt;/strong&gt; a live URL — and exits non-zero when the SEO and social-share tags are broken or missing, so the build goes red.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;atlashey-collab/seo-meta-action@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;index.html&lt;/span&gt;        &lt;span class="c1"&gt;# a file path OR a https:// URL&lt;/span&gt;
    &lt;span class="na"&gt;fail-on-warning&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;     &lt;span class="c1"&gt;# set true to also fail on warnings&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It checks the tags that actually move rankings and share previews:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; — present, non-empty, ~30-60 chars, exactly one&lt;/li&gt;
&lt;li&gt;meta description — present, ~120-160 chars, no duplicates&lt;/li&gt;
&lt;li&gt;exactly one &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;absolute &lt;code&gt;&amp;lt;link rel="canonical"&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;viewport, charset, &lt;code&gt;&amp;lt;html lang&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;accidental &lt;code&gt;robots: noindex&lt;/code&gt; (the silent traffic killer)&lt;/li&gt;
&lt;li&gt;Open Graph: &lt;code&gt;og:title&lt;/code&gt;, &lt;code&gt;og:description&lt;/code&gt;, &lt;code&gt;og:image&lt;/code&gt;, &lt;code&gt;og:url&lt;/code&gt;, &lt;code&gt;og:type&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;twitter:card&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Errors fail the job by default. Warnings only fail it when you flip &lt;code&gt;fail-on-warning: true&lt;/code&gt;, so you can adopt it gradually instead of turning your whole repo red on day one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two design choices that mattered
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;No dependencies.&lt;/strong&gt; It's a single Python 3 file parsed with the stdlib &lt;code&gt;html.parser&lt;/code&gt;. No &lt;code&gt;pip install&lt;/code&gt;, nothing to pin, nothing to get a CVE next quarter. You can also run it locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 validate_meta.py page.html
python3 validate_meta.py https://example.com/ &lt;span class="nt"&gt;--fail-on-warning&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Opinionated length windows.&lt;/strong&gt; Title 30-60 and description 120-160 are the windows Google renders before it truncates in desktop SERPs. The linter warns outside them instead of erroring, because a 62-char title isn't &lt;em&gt;broken&lt;/em&gt; — it's just getting clipped, and you should know.&lt;/p&gt;

&lt;h2&gt;
  
  
  It immediately caught my own bug
&lt;/h2&gt;

&lt;p&gt;The first thing I did was point it at my own site. It flagged a tools page with a 73-char title (truncated in search), a 186-char description (truncated), and — embarrassingly — a missing &lt;code&gt;og:image&lt;/code&gt; and &lt;code&gt;twitter:card&lt;/code&gt;. Four real issues I'd shipped and never noticed. Two-minute fix, but I'd never have caught them by eye.&lt;/p&gt;

&lt;p&gt;That's the whole pitch: the boring tags are the ones nobody re-checks after the first commit, and they're exactly the ones that quietly cost you clicks.&lt;/p&gt;

&lt;p&gt;It's MIT-licensed. Repo, full check list, and the local CLI are here: &lt;a href="https://github.com/atlashey-collab/seo-meta-action" rel="noopener noreferrer"&gt;https://github.com/atlashey-collab/seo-meta-action&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you bake it into a pipeline and hit an edge case it mis-flags, open an issue — I want the defaults to be genuinely correct, not just opinionated.&lt;/p&gt;

</description>
      <category>seo</category>
      <category>github</category>
      <category>webdev</category>
      <category>devops</category>
    </item>
    <item>
      <title>I built a GitHub Action that fails CI when your llms.txt is broken</title>
      <dc:creator>hey atlas</dc:creator>
      <pubDate>Fri, 26 Jun 2026 17:39:34 +0000</pubDate>
      <link>https://dev.to/hey_atlas_5d684863e9b069a/i-built-a-github-action-that-fails-ci-when-your-llmstxt-is-broken-54jg</link>
      <guid>https://dev.to/hey_atlas_5d684863e9b069a/i-built-a-github-action-that-fails-ci-when-your-llmstxt-is-broken-54jg</guid>
      <description>&lt;p&gt;If you have added an &lt;code&gt;llms.txt&lt;/code&gt; to your site, here is the uncomfortable part: nothing tells you when it breaks. A missing title, a malformed link, a relative URL that an AI fetcher cannot resolve, and your carefully curated file just gets skipped. Silently. So I built a tiny GitHub Action that lints &lt;code&gt;llms.txt&lt;/code&gt; on every push and fails the build when it is wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick refresher: what is llms.txt?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://llmstxt.org" rel="noopener noreferrer"&gt;&lt;code&gt;llms.txt&lt;/code&gt;&lt;/a&gt; is a small markdown file at the root of your site that hands large language models a curated map of your best pages. It is the AI-search cousin of &lt;code&gt;robots.txt&lt;/code&gt; and &lt;code&gt;sitemap.xml&lt;/code&gt;: instead of letting a crawler guess, you tell ChatGPT, Perplexity, Claude and Google AI Overviews exactly what to read and cite.&lt;/p&gt;

&lt;p&gt;The format is deliberately simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Your Site&lt;/span&gt;
&lt;span class="gt"&gt;
&amp;gt; One-line summary a model reads first.&lt;/span&gt;

&lt;span class="gu"&gt;## Section name&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Page title&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://example.com/page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;: short note on what it is.

&lt;span class="gu"&gt;## Optional&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Lower-priority page&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://example.com/extra&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;: models may skip this to save context.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The catch is that "simple" is not the same as "hard to get wrong". The H1 is the only strictly required element, links must be real markdown link bullets, and an &lt;code&gt;Optional&lt;/code&gt; section has special meaning. Those are exactly the things you forget at 1am.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a CI check
&lt;/h2&gt;

&lt;p&gt;I treat &lt;code&gt;llms.txt&lt;/code&gt; like any other build artifact. If a broken sitemap fails CI, a broken AI-readability file should too. The rules I wanted enforced:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Errors (break the build):&lt;/strong&gt; the file exists and is non-empty, there is exactly one H1 title and it comes first, every link bullet is well-formed &lt;code&gt;- [name](url): notes&lt;/code&gt;, and no URL is empty.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Warnings (optional break):&lt;/strong&gt; a blockquote summary sits right under the title, links use absolute &lt;code&gt;https://&lt;/code&gt; URLs, every link has a &lt;code&gt;: description&lt;/code&gt;, sections use H2, no empty sections, no duplicate URLs, and an &lt;code&gt;Optional&lt;/code&gt; section exists.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Action
&lt;/h2&gt;

&lt;p&gt;Zero dependencies, pure Python standard library, so it runs in about a second on a stock runner with no &lt;code&gt;setup&lt;/code&gt; step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Validate llms.txt&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;llms-txt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;atlashey-collab/llms-txt-action@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;public/llms.txt&lt;/span&gt;   &lt;span class="c1"&gt;# or a live URL like https://example.com&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can point &lt;code&gt;target&lt;/code&gt; at a file path or a deployed URL (a bare site URL gets &lt;code&gt;/llms.txt&lt;/code&gt; appended). Flip &lt;code&gt;fail-on-warning: true&lt;/code&gt; for strict mode. Every run drops a table of H1 / sections / links / errors / warnings into the job summary, with per-line messages.&lt;/p&gt;

&lt;p&gt;It is MIT licensed and the full validator is one readable file: &lt;a href="https://github.com/atlashey-collab/llms-txt-action" rel="noopener noreferrer"&gt;github.com/atlashey-collab/llms-txt-action&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run it locally too
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sO&lt;/span&gt; https://raw.githubusercontent.com/atlashey-collab/llms-txt-action/v1/validate_llms_txt.py
python3 validate_llms_txt.py llms.txt
python3 validate_llms_txt.py https://example.com &lt;span class="nt"&gt;--fail-on-warning&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exit codes are CI-friendly: &lt;code&gt;0&lt;/code&gt; valid, &lt;code&gt;1&lt;/code&gt; validation failed, &lt;code&gt;2&lt;/code&gt; usage error.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest caveat
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;llms.txt&lt;/code&gt; is a young convention. Adoption by the big AI engines is still uneven, and a valid file does not guarantee citations. But the cost of keeping it correct is now zero, and getting cited is impossible if the file is broken. That trade is easy.&lt;/p&gt;

&lt;p&gt;If you do not have one yet, write a spec-compliant file first, then wire up the check. Either way, stop shipping a broken &lt;code&gt;llms.txt&lt;/code&gt; and not knowing.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>seo</category>
      <category>github</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I built a GitHub Action that fails your CI when your Schema.org markup is broken</title>
      <dc:creator>hey atlas</dc:creator>
      <pubDate>Fri, 26 Jun 2026 13:05:15 +0000</pubDate>
      <link>https://dev.to/hey_atlas_5d684863e9b069a/i-built-a-github-action-that-fails-your-ci-when-your-schemaorg-markup-is-broken-5cmg</link>
      <guid>https://dev.to/hey_atlas_5d684863e9b069a/i-built-a-github-action-that-fails-your-ci-when-your-schemaorg-markup-is-broken-5cmg</guid>
      <description>&lt;p&gt;Google quietly retired the &lt;strong&gt;Structured Data Testing Tool&lt;/strong&gt; a while back, and the thing it replaced it with, the Rich Results Test, is a manual web form: rate-limited, and it only works on URLs that are already &lt;em&gt;live&lt;/em&gt;. So the usual workflow is "ship the page, then go find out the JSON-LD was broken." That is backwards.&lt;/p&gt;

&lt;p&gt;I wanted the check to live where every other quality gate lives: in CI, on every push, failing the build &lt;em&gt;before&lt;/em&gt; the broken markup reaches production. I couldn't find a clean zero-dependency one, so I wrote one and open-sourced it.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://github.com/atlashey-collab/structured-data-action" rel="noopener noreferrer"&gt;&lt;code&gt;structured-data-action&lt;/code&gt;&lt;/a&gt; validates the JSON-LD / Schema.org structured data on a page (or in a file, or a raw snippet) and fails the job when a &lt;strong&gt;required&lt;/strong&gt; rich-result property is missing.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One Python file, &lt;strong&gt;standard library only&lt;/strong&gt; (3.8+). No &lt;code&gt;pip install&lt;/code&gt;, no node_modules.&lt;/li&gt;
&lt;li&gt;Validates &lt;strong&gt;20+ types&lt;/strong&gt; Google uses for rich results: Article, FAQPage, Product, Offer, Organization, LocalBusiness, BreadcrumbList, HowTo, Review, Recipe, Event, VideoObject, JobPosting, WebSite, and more.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Errors vs. warnings.&lt;/strong&gt; Missing a &lt;em&gt;required&lt;/em&gt; prop = error (the rich result is broken). Missing a &lt;em&gt;recommended&lt;/em&gt; prop = warning (eligible, but weaker).&lt;/li&gt;
&lt;li&gt;Extracts &lt;strong&gt;every&lt;/strong&gt; &lt;code&gt;&amp;lt;script type="application/ld+json"&amp;gt;&lt;/code&gt; block on the page, including objects nested inside &lt;code&gt;@graph&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;CI-native: GitHub annotations on the exact file, a job-summary table, and meaningful exit codes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Drop it in a workflow
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SEO checks&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;structured-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;atlashey-collab/structured-data-action@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dist/**/*.html'&lt;/span&gt;   &lt;span class="c1"&gt;# a URL, an HTML file, or a glob&lt;/span&gt;
          &lt;span class="na"&gt;fail-on-error&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;true'&lt;/span&gt;      &lt;span class="c1"&gt;# break the build if required props are missing&lt;/span&gt;
          &lt;span class="na"&gt;fail-on-warning&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;false'&lt;/span&gt;   &lt;span class="c1"&gt;# flip to true to also fail on missing recommended props&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Point &lt;code&gt;target&lt;/code&gt; at a live URL instead of a glob and it'll fetch and check that page directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Or just run it locally
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 validate_schema.py https://example.com
python3 validate_schema.py &lt;span class="s2"&gt;"dist/**/*.html"&lt;/span&gt; &lt;span class="nt"&gt;--fail-on-error&lt;/span&gt;
python3 validate_schema.py snippet.json &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;• https://example.com/product
  ✓ Product / merchant listing — complete
  • Offer
      ✗ missing required: priceCurrency
      ⚠ missing recommended: availability
  ✓ FAQ rich result — complete

Summary: 1 required (error) and 1 recommended (warning) properties missing.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How it decides
&lt;/h2&gt;

&lt;p&gt;For each typed object, it checks the properties Google documents for that type. Required missing → error (ineligible). Recommended missing → warning (qualifies, shown with less detail). It also enforces a few high-value extras, e.g. a &lt;code&gt;Product&lt;/code&gt; must carry at least one of &lt;code&gt;offers&lt;/code&gt;, &lt;code&gt;review&lt;/code&gt;, or &lt;code&gt;aggregateRating&lt;/code&gt;, which is the single most common reason a product snippet silently doesn't show.&lt;/p&gt;

&lt;p&gt;It's a fast structural pre-flight, not a renderer. For final "will Google actually draw the rich result on this live URL" confirmation, still run Google's Rich Results Test once before launch. Use this on every commit; use Google's tool for the final sign-off.&lt;/p&gt;

&lt;h2&gt;
  
  
  One more reason it matters in 2026
&lt;/h2&gt;

&lt;p&gt;The same JSON-LD that earns Google rich results also helps AI answer engines (ChatGPT, Perplexity, Gemini) parse and cite your pages. Broken schema is a silent tax on both classic SEO &lt;em&gt;and&lt;/em&gt; AI-search visibility. A CI gate keeps it honest.&lt;/p&gt;

&lt;p&gt;It's MIT licensed, issues and PRs welcome: &lt;strong&gt;&lt;a href="https://github.com/atlashey-collab/structured-data-action" rel="noopener noreferrer"&gt;https://github.com/atlashey-collab/structured-data-action&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There's also a free in-browser version if you just want to paste a snippet and eyeball it: &lt;a href="https://aitoolsinsiderhq.com/schema-validator.html" rel="noopener noreferrer"&gt;schema-validator&lt;/a&gt;. The Action keeps the same rule set, just wired into your pipeline.&lt;/p&gt;

</description>
      <category>seo</category>
      <category>webdev</category>
      <category>github</category>
      <category>python</category>
    </item>
    <item>
      <title>I read the robots.txt of 41 top AI tools. 88% block nothing, and the rest mostly block the wrong bots.</title>
      <dc:creator>hey atlas</dc:creator>
      <pubDate>Fri, 26 Jun 2026 10:09:35 +0000</pubDate>
      <link>https://dev.to/hey_atlas_5d684863e9b069a/i-read-the-robotstxt-of-41-top-ai-tools-88-block-nothing-and-the-rest-mostly-block-the-wrong-ph3</link>
      <guid>https://dev.to/hey_atlas_5d684863e9b069a/i-read-the-robotstxt-of-41-top-ai-tools-88-block-nothing-and-the-rest-mostly-block-the-wrong-ph3</guid>
      <description>&lt;p&gt;&lt;em&gt;Everyone assumes the big AI tools are racing to wall their data off from crawlers. I got curious about whether that's actually true, so I read the live &lt;code&gt;robots.txt&lt;/code&gt; of 41 well-known AI and SaaS tools and scored each against the 10 biggest AI crawlers. The result surprised me.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The headline
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;36 of 41 tools (88%) block no AI crawler at all.&lt;/strong&gt; GPTBot, ClaudeBot, PerplexityBot, OAI-SearchBot, the lot, all welcome. Only 5 tools block anything, and GPTBot is the single most-blocked bot, at a whopping 7% of sites.&lt;/p&gt;

&lt;p&gt;So the "AI companies are locking everything down" narrative basically doesn't show up in the &lt;code&gt;robots.txt&lt;/code&gt; of the tools themselves. For a marketing site, being readable is how you get &lt;em&gt;cited&lt;/em&gt; in an AI answer, and a citation is the new top of the funnel. Most of them seem to have figured that out.&lt;/p&gt;

&lt;h2&gt;
  
  
  The distinction almost everyone gets wrong
&lt;/h2&gt;

&lt;p&gt;There are two kinds of AI crawler, and they are not the same thing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Citation crawlers&lt;/strong&gt; (&lt;code&gt;OAI-SearchBot&lt;/code&gt;, &lt;code&gt;ChatGPT-User&lt;/code&gt;, &lt;code&gt;PerplexityBot&lt;/code&gt;, &lt;code&gt;Perplexity-User&lt;/code&gt;, &lt;code&gt;Claude-User&lt;/code&gt;) fetch a page so an answer engine can quote and link it. Block these and you vanish from AI search. That's pure lost traffic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Training crawlers&lt;/strong&gt; (&lt;code&gt;GPTBot&lt;/code&gt;, &lt;code&gt;ClaudeBot&lt;/code&gt;, &lt;code&gt;CCBot&lt;/code&gt;, &lt;code&gt;Bytespider&lt;/code&gt;, &lt;code&gt;Google-Extended&lt;/code&gt;) collect pages to train a model. Block these and you lose... nothing in traffic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The smart &lt;code&gt;robots.txt&lt;/code&gt; blocks training bots and allows citation bots. In my sample, citation bots were blocked by at most one site each, while the blocks that &lt;em&gt;did&lt;/em&gt; exist were aimed mostly at training. So collectively, the field is getting it right.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two outliers tell the whole story
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Figma&lt;/strong&gt; is the strictest, and for AI search the most self-defeating: it blocks six crawlers &lt;em&gt;including the citation bots&lt;/em&gt;. Net effect: Figma's own pages can't be surfaced or cited inside ChatGPT or Perplexity answers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Canva&lt;/strong&gt; blocks four bots, but &lt;em&gt;only training ones&lt;/em&gt; (&lt;code&gt;GPTBot&lt;/code&gt;, &lt;code&gt;ClaudeBot&lt;/code&gt;, &lt;code&gt;CCBot&lt;/code&gt;, &lt;code&gt;Bytespider&lt;/code&gt;) while leaving citation bots open. That's the textbook-correct move: deny the free training data, keep the AI-search visibility.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same instinct ("don't feed the machines for free"), opposite outcome, because one of them knew which bots to target.&lt;/p&gt;

&lt;h2&gt;
  
  
  A &lt;code&gt;robots.txt&lt;/code&gt; that does it right
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="c"&gt;# Deny free training
&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;-&lt;span class="n"&gt;agent&lt;/span&gt;: &lt;span class="n"&gt;GPTBot&lt;/span&gt;
&lt;span class="n"&gt;Disallow&lt;/span&gt;: /
&lt;span class="n"&gt;User&lt;/span&gt;-&lt;span class="n"&gt;agent&lt;/span&gt;: &lt;span class="n"&gt;CCBot&lt;/span&gt;
&lt;span class="n"&gt;Disallow&lt;/span&gt;: /
&lt;span class="n"&gt;User&lt;/span&gt;-&lt;span class="n"&gt;agent&lt;/span&gt;: &lt;span class="n"&gt;Google&lt;/span&gt;-&lt;span class="n"&gt;Extended&lt;/span&gt;
&lt;span class="n"&gt;Disallow&lt;/span&gt;: /

&lt;span class="c"&gt;# Keep AI-search citations
&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;-&lt;span class="n"&gt;agent&lt;/span&gt;: &lt;span class="n"&gt;OAI&lt;/span&gt;-&lt;span class="n"&gt;SearchBot&lt;/span&gt;
&lt;span class="n"&gt;Allow&lt;/span&gt;: /
&lt;span class="n"&gt;User&lt;/span&gt;-&lt;span class="n"&gt;agent&lt;/span&gt;: &lt;span class="n"&gt;ChatGPT&lt;/span&gt;-&lt;span class="n"&gt;User&lt;/span&gt;
&lt;span class="n"&gt;Allow&lt;/span&gt;: /
&lt;span class="n"&gt;User&lt;/span&gt;-&lt;span class="n"&gt;agent&lt;/span&gt;: &lt;span class="n"&gt;PerplexityBot&lt;/span&gt;
&lt;span class="n"&gt;Allow&lt;/span&gt;: /
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Check your own site
&lt;/h2&gt;

&lt;p&gt;I pulled this together with a free tool I built that does the parse for you. Paste your &lt;code&gt;robots.txt&lt;/code&gt; and it flags all 18 major AI bots, tags each as citation vs training, and spits out a recommended file: &lt;strong&gt;&lt;a href="https://aitoolsinsiderhq.com/ai-crawler-access-checker.html" rel="noopener noreferrer"&gt;AI Crawler &amp;amp; robots.txt Access Checker&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Full study, per-bot table, and the dataset (CC BY 4.0, reuse it freely): &lt;strong&gt;&lt;a href="https://aitoolsinsiderhq.com/ai-crawler-study.html" rel="noopener noreferrer"&gt;Who Blocks ChatGPT, Claude &amp;amp; Perplexity?&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What does &lt;em&gt;your&lt;/em&gt; &lt;code&gt;robots.txt&lt;/code&gt; block? Most people I've shown this to were blocking the wrong half.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>seo</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>X killed its Card Validator and Facebook's needs a login, so I built a free Open Graph preview tool</title>
      <dc:creator>hey atlas</dc:creator>
      <pubDate>Fri, 26 Jun 2026 03:06:21 +0000</pubDate>
      <link>https://dev.to/hey_atlas_5d684863e9b069a/x-killed-its-card-validator-and-facebooks-needs-a-login-so-i-built-a-free-open-graph-preview-tool-3cao</link>
      <guid>https://dev.to/hey_atlas_5d684863e9b069a/x-killed-its-card-validator-and-facebooks-needs-a-login-so-i-built-a-free-open-graph-preview-tool-3cao</guid>
      <description>&lt;p&gt;When I push a new page, I want to know how the link card looks &lt;em&gt;before&lt;/em&gt; I share it. That used to mean two tools: Twitter's Card Validator and Facebook's Sharing Debugger. In 2026 both are basically gone for that job:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;X retired its public Card Validator&lt;/strong&gt; (cards.twitter.com/validator) back in 2023. There is no first-party preview anymore.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Facebook's Sharing Debugger&lt;/strong&gt; still exists, but it needs a login &lt;em&gt;and&lt;/em&gt; it only works on URLs that are already public. Useless for a staging or unpublished page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So you are flying blind on the exact moment that decides whether anyone clicks. I got tired of it and built a free tool that reads HTML you paste and renders the Facebook, X, LinkedIn and Google previews locally, plus a checklist of what is missing. No login, works on unpublished pages, nothing leaves the browser: &lt;strong&gt;&lt;a href="https://aitoolsinsiderhq.com/open-graph-preview.html" rel="noopener noreferrer"&gt;Open Graph &amp;amp; Social Share Preview Validator&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This post is the 5-minute version of what it checks, so you can fix your tags whether or not you use the tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tags that actually matter
&lt;/h2&gt;

&lt;p&gt;A broken share card is almost always one of these four:&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="c"&gt;&amp;lt;!-- Open Graph: Facebook, LinkedIn, Slack, Discord, iMessage... --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:title"&lt;/span&gt;       &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"The 7 Best Free AI Tools in 2026 (Tested)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:description"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"The seven free AI tools actually worth your time in 2026."&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:image"&lt;/span&gt;       &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"https://example.com/og-image.jpg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:url"&lt;/span&gt;         &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"https://example.com/best-free-ai-tools"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:type"&lt;/span&gt;        &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"article"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:site_name"&lt;/span&gt;   &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"Example"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Twitter / X --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"twitter:card"&lt;/span&gt;        &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"summary_large_image"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"twitter:title"&lt;/span&gt;       &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"The 7 Best Free AI Tools in 2026 (Tested)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"twitter:description"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"The seven free AI tools actually worth your time in 2026."&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"twitter:image"&lt;/span&gt;      &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"https://example.com/og-image.jpg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The four mistakes I see most
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;og:image&lt;/code&gt; is missing or relative.&lt;/strong&gt; This is the number one reason a link shares as a bare grey box. The URL must be absolute (&lt;code&gt;https://...&lt;/code&gt;), not &lt;code&gt;/og-image.jpg&lt;/code&gt;. Aim for 1200x630px.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No &lt;code&gt;twitter:card&lt;/code&gt;.&lt;/strong&gt; Without &lt;code&gt;summary_large_image&lt;/code&gt; you get a tiny thumbnail on X instead of the big image card. X falls back to OG tags for title/description, but not for the card &lt;em&gt;type&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Title and description too long.&lt;/strong&gt; The card crops around ~60 chars for the title and ~155 for the description. Write for the crop, not the full sentence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;og:url&lt;/code&gt; and &lt;code&gt;&amp;lt;link rel="canonical"&amp;gt;&lt;/code&gt; disagree.&lt;/strong&gt; When they point at different URLs, your share equity and your SEO equity split across two addresses. Keep them identical.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A useful mental model: &lt;code&gt;og:*&lt;/code&gt; is the canonical set, and most platforms fall back to it. &lt;code&gt;twitter:*&lt;/code&gt; only overrides when present. So you can ship a solid card with OG alone and add the Twitter tags only where you want different copy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I check this locally instead of with a live fetcher
&lt;/h2&gt;

&lt;p&gt;A server-side fetcher cannot see a page that is not deployed yet, and that is exactly when I want the preview. Parsing pasted HTML with &lt;code&gt;DOMParser&lt;/code&gt; in the browser means it works on localhost, on a staging branch, or on a draft in your CMS, with zero round trips and nothing uploaded. The tool also spits out a corrected tag block you can paste straight into &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;, with the gaps filled from whatever fallbacks it can find.&lt;/p&gt;

&lt;p&gt;If you build sites, bookmark a preview tool you can hit without logging into anyone's platform. Here is the one I made: &lt;strong&gt;&lt;a href="https://aitoolsinsiderhq.com/open-graph-preview.html" rel="noopener noreferrer"&gt;open-graph-preview&lt;/a&gt;&lt;/strong&gt; (free, no signup).&lt;/p&gt;

&lt;p&gt;What is your biggest OG-tag gotcha? The hotlink-blocked image one still gets me on certain CDNs.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>html</category>
      <category>seo</category>
      <category>tools</category>
    </item>
    <item>
      <title>I built a free JSON-LD schema validator (since Google retired the Structured Data Testing Tool)</title>
      <dc:creator>hey atlas</dc:creator>
      <pubDate>Fri, 26 Jun 2026 01:07:24 +0000</pubDate>
      <link>https://dev.to/hey_atlas_5d684863e9b069a/i-built-a-free-json-ld-schema-validator-since-google-retired-the-structured-data-testing-tool-2a2i</link>
      <guid>https://dev.to/hey_atlas_5d684863e9b069a/i-built-a-free-json-ld-schema-validator-since-google-retired-the-structured-data-testing-tool-2a2i</guid>
      <description>&lt;p&gt;When Google retired the Structured Data Testing Tool and replaced it with the rate-limited Rich Results Test, the fast feedback loop for fixing JSON-LD got worse. The Rich Results Test renders your live page, which is great for a final check but slow when you are iterating on a snippet and just want to know which property you forgot.&lt;/p&gt;

&lt;p&gt;So I built a small, dependency-free validator that runs entirely in the browser: paste a JSON-LD snippet or a whole HTML page, and it tells you, per object, what is missing. No signup, no rate limit, nothing uploaded.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tool:&lt;/strong&gt; &lt;a href="https://aitoolsinsiderhq.com/schema-validator.html" rel="noopener noreferrer"&gt;https://aitoolsinsiderhq.com/schema-validator.html&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that actually trips people up
&lt;/h2&gt;

&lt;p&gt;Schema validity has two layers, and conflating them is where most "my rich result won't show" bugs come from:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Is it valid JSON-LD?&lt;/strong&gt; (parses, has &lt;code&gt;@context&lt;/code&gt; and &lt;code&gt;@type&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Does it have the properties Google needs for &lt;em&gt;that&lt;/em&gt; rich result?&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Your markup can pass layer 1 and still be invisible in search because it fails layer 2. A &lt;code&gt;Product&lt;/code&gt; that parses perfectly but has no &lt;code&gt;offers&lt;/code&gt;, &lt;code&gt;review&lt;/code&gt;, or &lt;code&gt;aggregateRating&lt;/code&gt; is not eligible for a product rich result, full stop.&lt;/p&gt;

&lt;h2&gt;
  
  
  The validation model
&lt;/h2&gt;

&lt;p&gt;The core is a per-type ruleset of required (error) vs recommended (warning) properties, mirroring Google's structured-data docs:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RULES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;req&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;headline&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
               &lt;span class="na"&gt;rec&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;author&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;datePublished&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;dateModified&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;image&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="na"&gt;FAQPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;req&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;mainEntity&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="na"&gt;rec&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="na"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;req&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;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
               &lt;span class="na"&gt;rec&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;image&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;brand&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;offers&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;aggregateRating&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;review&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="c1"&gt;// ...Organization, BreadcrumbList, HowTo, Review, Recipe, Event, VideoObject...&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two implementation details that matter more than they look:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Walk the whole tree, including &lt;code&gt;@graph&lt;/code&gt;.&lt;/strong&gt; Real pages rarely ship one flat object. They ship a &lt;code&gt;@graph&lt;/code&gt; array, with nested &lt;code&gt;Offer&lt;/code&gt;, &lt;code&gt;AggregateRating&lt;/code&gt;, &lt;code&gt;ListItem&lt;/code&gt;, and &lt;code&gt;Question&lt;/code&gt; objects. If you only check the top-level &lt;code&gt;@type&lt;/code&gt; you miss most of the document. A recursive collector that descends into &lt;code&gt;@graph&lt;/code&gt; and into any nested object with its own &lt;code&gt;@type&lt;/code&gt; catches them all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Present" is not the same as "truthy-and-non-empty".&lt;/strong&gt; &lt;code&gt;"image": ""&lt;/code&gt; or &lt;code&gt;"mainEntity": []&lt;/code&gt; will pass a naive &lt;code&gt;prop in obj&lt;/code&gt; check and then fail in the wild. The check has to reject empty strings and empty arrays:&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prop&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="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prop&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&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;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;v&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&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;&lt;strong&gt;Conditional requirements need special cases.&lt;/strong&gt; &lt;code&gt;Product&lt;/code&gt; is the classic: it needs &lt;em&gt;at least one of&lt;/em&gt; &lt;code&gt;offers&lt;/code&gt;, &lt;code&gt;review&lt;/code&gt;, or &lt;code&gt;aggregateRating&lt;/code&gt;. A flat required-list cannot express "one of these three", so that rule gets its own branch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things I learned wiring this onto a real site
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A surprising number of FAQPage blocks have a &lt;code&gt;mainEntity&lt;/code&gt; array where individual &lt;code&gt;Question&lt;/code&gt; objects are missing &lt;code&gt;acceptedAnswer&lt;/code&gt;, so they silently drop out of the FAQ result.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BreadcrumbList&lt;/code&gt; items need &lt;code&gt;position&lt;/code&gt; &lt;em&gt;and&lt;/em&gt; &lt;code&gt;name&lt;/code&gt;; people often ship &lt;code&gt;item&lt;/code&gt; URLs with no &lt;code&gt;name&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Smart quotes from a CMS are the number one cause of "valid in my editor, invalid in production" JSON-LD. The parser error message is the fastest way to spot them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is client-side and free, so if it is useful, use it on your unpublished drafts too. And if you find a type or property check that is wrong or missing, I would genuinely like to know.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I write about AI tools and AI-search visibility at &lt;a href="https://aitoolsinsiderhq.com/free-seo-tools.html" rel="noopener noreferrer"&gt;AI Tools Insider&lt;/a&gt;, where this validator and a few other free no-signup tools live.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>seo</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>google</category>
    </item>
    <item>
      <title>I open-sourced my AI tools pricing dataset (31 tools, JSON, CC BY 4.0)</title>
      <dc:creator>hey atlas</dc:creator>
      <pubDate>Thu, 25 Jun 2026 21:08:09 +0000</pubDate>
      <link>https://dev.to/hey_atlas_5d684863e9b069a/i-open-sourced-my-ai-tools-pricing-dataset-31-tools-json-cc-by-40-14fi</link>
      <guid>https://dev.to/hey_atlas_5d684863e9b069a/i-open-sourced-my-ai-tools-pricing-dataset-31-tools-json-cc-by-40-14fi</guid>
      <description>&lt;p&gt;I kept needing the same thing while writing AI tool comparisons: a clean, current list of what these tools actually cost. Not a screenshot from a pricing page that changed last week. Not a "starts at $$$" marketing line. Real, structured numbers I could sort, chart, and reason about.&lt;/p&gt;

&lt;p&gt;So I built one, and now I've published it as open data.&lt;/p&gt;

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

&lt;p&gt;A single JSON file with every AI and productivity tool I've reviewed hands-on, one record each:&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;"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;"Claude"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AI Assistants"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"free_tier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Yes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"entry_price_display"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$20/mo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"entry_price_usd_monthly"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"best_for"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Long-form writing, analysis, and coding"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"review_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://aitoolsinsiderhq.com/articles/claude-ai-review-2026.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"editor_pick"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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;31 tools across 11 categories (AI assistants, writing, SEO, design, email, CRM, productivity, project management, video/audio, automation, and one piece of hardware). It ships with a &lt;code&gt;summary&lt;/code&gt; block too, so you don't have to compute the obvious aggregates yourself:&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="nl"&gt;"summary"&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;"tools_total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"software_tools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"average_entry_price_usd_monthly"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;24.66&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"median_entry_price_usd_monthly"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;15.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"with_free_tier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"with_free_tier_pct"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;93&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;Grab it here: &lt;strong&gt;&lt;a href="https://aitoolsinsiderhq.com/ai-tools-dataset.json" rel="noopener noreferrer"&gt;https://aitoolsinsiderhq.com/ai-tools-dataset.json&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why JSON and not another blog table
&lt;/h2&gt;

&lt;p&gt;Because data you can &lt;code&gt;fetch()&lt;/code&gt; is data you can actually use. A few things I do with it:&lt;/p&gt;

&lt;p&gt;Compute the spread in three lines of Python:&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;urllib.request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://aitoolsinsiderhq.com/ai-tools-dataset.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;paid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;entry_price_usd_monthly&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;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tools&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="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;free_tier&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;N/A&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&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="s"&gt;avg $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paid&lt;/span&gt;&lt;span class="p"&gt;)&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;paid&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="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  cheapest $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  priciest $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&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;Or pull it straight into a frontend:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&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://aitoolsinsiderhq.com/ai-tools-dataset.json&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;tools&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;seo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tools&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;t&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SEO&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entry_price_usd_monthly&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entry_price_usd_monthly&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The interesting findings fall out fast: the median entry plan is &lt;strong&gt;$15.50/mo&lt;/strong&gt; but the mean is &lt;strong&gt;$24.66&lt;/strong&gt; because a handful of SEO suites drag the average up; &lt;strong&gt;93%&lt;/strong&gt; of these tools have some free tier; project management is the cheapest category and SEO is the most expensive. None of that is surprising in isolation, but having it as queryable data makes it easy to fact-check a claim or build something on top.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't want to parse it? Embed the live table
&lt;/h2&gt;

&lt;p&gt;If you're writing a post and just want a maintained comparison table that updates when prices change, the rendered version is embeddable in one line:&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;iframe&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://aitoolsinsiderhq.com/ai-tools-database.html?embed=1"&lt;/span&gt;
        &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"100%"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"720"&lt;/span&gt; &lt;span class="na"&gt;loading=&lt;/span&gt;&lt;span class="s"&gt;"lazy"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"border:0"&lt;/span&gt;
        &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"AI tools comparison"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It strips the site chrome and shows a sortable, filterable table with a small "powered by" link back. I'd rather you drop that in than copy a static table that's wrong in a month.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest caveats
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Prices are the &lt;strong&gt;published entry-level paid plan for 2026&lt;/strong&gt; (annual billing where that's the headline), pulled from my reviews. They're a starting reference, not a live quote. Vendors change plans; confirm on the tool's own site before quoting a number in something important.&lt;/li&gt;
&lt;li&gt;It covers tools I've actually used and written up, so it's opinionated by construction, not an exhaustive market scrape. &lt;code&gt;editor_pick&lt;/code&gt; is just that: my pick, flagged honestly so you can ignore it.&lt;/li&gt;
&lt;li&gt;One hardware item has &lt;code&gt;free_tier: "N/A"&lt;/code&gt; and is excluded from the price aggregates.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  License
&lt;/h2&gt;

&lt;p&gt;CC BY 4.0. Use any of it, in a post, a chart, a side project, a model eval set, whatever. Just attribute it with a link back to &lt;a href="https://aitoolsinsiderhq.com/" rel="noopener noreferrer"&gt;https://aitoolsinsiderhq.com/&lt;/a&gt;. That's the whole deal.&lt;/p&gt;

&lt;p&gt;I regenerate the file from the same source the comparison site is built from, so it can't silently drift from the pages. If a price looks off, tell me and I'll fix it at the source. And if you build something fun with it, I'd genuinely like to see it.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>datascience</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
